通过引用传递va_list的跨平台方法是什么?



我写了一个接受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_integersva_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_argprint_integer导致print_integersap不确定。在一个架构上,它正在递增,而在另一个架构上,它没有。

至于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)但是编译器拒绝了,编译器会检查所有输入的所有分支中的约束违规 - 尽管不要继续检查周围表达式中未选定分支的约束冲突。

最新更新