我编写了一个接受va_list
的函数,该函数打算由其调用方迭代地调用。它应该修改va_list
,并且更改应继续存在于调用方中,以便对函数的下一次调用将继续使用下一个参数。
我无法具体发布该代码,但这是一个重现此情况的代码段(godbolt link):
#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
自变量衰减为a指针)。但是,它在我的ARM平台上(打印“ 1 1 1”)不起作用,其中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_list
可以很容易地为va_copy
获得一致的跨平台值语义。是否有一种跨平台的方法来获取va_list
的一致引用语义?
答案 0 :(得分:1)
这里要讨论的问题是,在x86平台上,va_list
被定义为1个元素的数组(我们称之为__va_list_tag[1]
)。当作为参数接受时,它会衰减为指针,因此&ap
取决于ap
是函数(__va_list_tag**
)的参数还是局部变量({{1} }。
一种适用于这种情况的解决方案是简单地创建一个本地__va_list_tag(*)[1]
,使用va_list
进行填充,然后将指针传递给该本地va_copy
。 (godbolt)
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
包装在一个结构中并传递一个指向该结构的指针。
答案 1 :(得分:0)
您可能不走运。 7.15(3)表示您的操作具有不确定的效果。
对象
ap
可以作为参数传递给另一个函数。如果该函数使用参数va_arg
调用ap
宏,则调用函数中ap
的值是不确定的,应在进一步引用之前传递给va_end
宏到ap
。
在va_arg
中对print_integer
的调用导致ap
中的print_integers
不确定。在一种体系结构中,它正在增加,而在另一种体系结构中,则没有。
关于va_list
是什么,它可以是任何东西...
...是适合于保存宏va_start,va_arg,va_end和va_copy所需信息的对象类型。
You may wish to consider a different approach to solving the underlying problem。
答案 2 :(得分:0)
第二个程序中的问题是,如果va_list
是数组类型,那么函数参数ap
的类型 adjusted 就是指针类型。因此,在每种情况下,该参数都有不同的间接级别。
自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));
}
此解决方案的灵感来自this answer,它在需要手动配置的两种情况下都使用了宏。
关于_Generic
的注释:
&ap
,因为自C18以来,对第一个表达式执行了数组到指针的衰减。_Generic(&ap, va_list*: &ap, default: (va_list *)ap)
,但是编译器拒绝了这一点,编译器会检查所有输入中所有分支的约束违规情况-尽管不会继续检查周围表达式中未选择分支的约束违规情况。