我写了一个接受va_list
的函数,它的意思是被它的调用者迭代调用。它应该修改va_list
并且更改应该保留在调用方中,以便下一次调用函数将继续下一个参数。
我无法专门发布该代码,但这里有一个重现这种情况的片段(godbolt 链接):
#include <stdarg.h>
#include <stdio.h>
void print_integer(va_list ap) {
printf("%in", 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平台上不起作用(打印"1 1 1"),va_list
似乎被定义为指向某物的指针。在该平台上,va_arg
始终返回第一个参数。
下一个最佳选择似乎是将ap
作为一个指针:
#include <stdarg.h>
#include <stdio.h>
void print_integer(va_list *ap) {
printf("%in", 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
导致print_integers
ap
不确定。在一个架构上,它正在递增,而在另一个架构上,它没有。
至于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)
但是编译器拒绝了,编译器会检查所有输入的所有分支中的约束违规 - 尽管不要继续检查周围表达式中未选定分支的约束冲突。