[英]What is the cross-platform way to pass a va_list by reference?
我編寫了一個接受va_list
的函數,該函數旨在由其調用者迭代調用。 它應該修改va_list
並且更改應該保留在調用者中,以便對該函數的下一次調用將繼續下一個參數。
我無法具體發布該代碼,但這里有一個重現這種情況的片段( godbolt 鏈接):
#include <stdarg.h>
#include <stdio.h>
void print_integer(va_list ap) {
printf("%i\n", va_arg(ap, int));
}
void vprint_integers(int count, va_list ap) {
for (int i = 0; i < count; ++i) {
print_integer(ap);
}
}
void print_integers(int count, ...) {
va_list ap;
va_start(ap, count);
vprint_integers(count, ap);
va_end(ap);
}
int main() {
print_integers(3, 1, 2, 3);
}
這在我的 x86 平台上有效(打印“1 2 3”),因為va_list
是通過“引用”傳遞的(它可能被聲明為一個元素的數組,因此va_list
參數衰減為指針)。 但是,它不工作對我的ARM平台(打印“111”),其中va_list
似乎被定義為指針的東西。 在那個平台上, va_arg
總是返回第一個參數。
下一個最好的選擇似乎是讓ap
成為一個指針:
#include <stdarg.h>
#include <stdio.h>
void print_integer(va_list *ap) {
printf("%i\n", va_arg(*ap, int));
}
void vprint_integers(int count, va_list ap) {
for (int i = 0; i < count; ++i) {
print_integer(&ap);
}
}
void print_integers(int count, ...) {
va_list ap;
va_start(ap, count);
vprint_integers(count, ap);
va_end(ap);
}
int main() {
print_integers(3, 1, 2, 3);
}
這適用於 ARM(使用va_arg(*ap, ...)
),但不能在 x86 上編譯。 當我在 x86 上嘗試print_integer(&ap)
時,Clang 說:
錯誤:不兼容的指針類型將“
struct __va_list_tag **
”傳遞給“va_list *
”類型的參數(又名“__builtin_va_list *
”)
這似乎只發生在將va_list
的地址作為參數傳遞時,而不是從局部變量中獲取時。 不幸的是,我確實需要我的v
變體來獲取va_list
對象而不是指向它的指針。
使用va_copy
很容易為va_list
獲得一致的跨平台值語義。 有沒有一種跨平台的方式來為va_list
獲得一致的引用語義?
這里的問題是va_list
在 x86 平台上被定義為一個包含 1 個元素的數組(我們稱之為__va_list_tag[1]
)。 當作為參數接受時,它會衰減為指針,因此&ap
有很大不同,具體取決於ap
是函數的參數( __va_list_tag**
)還是局部變量( __va_list_tag(*)[1]
)。
適用於這種情況的一種解決方案是簡單地創建一個本地va_list
,使用va_copy
來填充它,然后將一個指針傳遞給這個本地va_list
。 (天馬行空)
void vprint_integers(int count, va_list ap) {
va_list local;
va_copy(local, ap);
for (int i = 0; i < count; ++i) {
print_integer(&local);
}
va_end(local);
}
就我而言, vprint_integers
和va_copy
是必要的,因為該接口vprint_integers
接受va_list
和不能改變。 對於更靈活的要求,更改vprint_integers
以接受va_list
指針也很好。
va_list
沒有被指定為任何特別的東西,但它被定義為一個對象類型,所以沒有理由相信你不能獲取或傳遞它的地址。 另一個完全繞過是否可以獲取va_list
地址問題的非常相似的解決方案是將va_list
包裝在一個結構中並傳遞一個指向該結構的指針。
你可能不走運。 7.15(3) 說你正在做的事情具有不確定的效果。
對象
ap
可以作為參數傳遞給另一個函數; 如果該函數使用參數ap
調用va_arg
宏,則調用函數中ap
的值是不確定的,應在進一步引用ap
之前傳遞給va_end
宏。
要調用va_arg
在print_integer
造成ap
在print_integers
是不確定的。 在一種架構上它是遞增的,而在另一種架構上則不是。
至於va_list
是什么,它可以是任何東西......
...這是一種適合保存宏 va_start、va_arg、va_end 和 va_copy 所需信息的對象類型。
第二個程序的問題是如果va_list
是數組類型,那么函數參數ap
的類型被調整為指針類型。 因此,在每種情況下,該參數都有不同的間接級別。
從 C11 開始,我們可以使用通用選擇器來解決這個問題,以測試ap
是否仍然是va_list
:
void vprint_integers(int count, va_list ap) {
for (int i = 0; i < count; ++i) {
print_integer((va_list *)_Generic(&ap, va_list*: &ap, default: ap));
}
這個解決方案的靈感來自這個答案,它在需要手動配置的兩種情況下使用了一個宏。
關於_Generic
注意事項:
&ap
,因為從 C18 開始,數組到指針的衰減是在第一個表達式上執行的。_Generic(&ap, va_list*: &ap, default: (va_list *)ap)
但是這被編譯器拒絕,編譯器檢查所有輸入的所有分支中的約束違規 - 盡管不要繼續檢查約束違規未選定分支的周圍表達式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.