[英]What is the cross-platform way to pass a va_list by reference?
I wrote a function that accepts a va_list
, and that is meant to be invoked iteratively by its caller.我编写了一个接受
va_list
的函数,该函数旨在由其调用者迭代调用。 It should modify the va_list
and changes should persist back in the caller so that the next call to the function will proceed with the next argument.它应该修改
va_list
并且更改应该保留在调用者中,以便对该函数的下一次调用将继续下一个参数。
I can't post that code specifically, but here's a snippet that reproduces the situation ( godbolt link ):我无法具体发布该代码,但这里有一个重现这种情况的片段( 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);
}
This works (prints "1 2 3") on my x86 platform because va_list
is passed by "reference" (it's probably declared as an array of one element, so va_list
arguments decay to a pointer).这在我的 x86 平台上有效(打印“1 2 3”),因为
va_list
是通过“引用”传递的(它可能被声明为一个元素的数组,因此va_list
参数衰减为指针)。 However, it does not work on my ARM platform (prints "1 1 1"), where va_list
seems to be defined as a pointer to something.但是,它不工作对我的ARM平台(打印“111”),其中
va_list
似乎被定义为指针的东西。 On that platform, va_arg
always returns the first argument.在那个平台上,
va_arg
总是返回第一个参数。
The next best option seems to be to make ap
a pointer:下一个最好的选择似乎是让
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);
}
This works on ARM (with va_arg(*ap, ...)
), but it does not compile on x86.这适用于 ARM(使用
va_arg(*ap, ...)
),但不能在 x86 上编译。 When I try print_integer(&ap)
on x86, Clang says:当我在 x86 上尝试
print_integer(&ap)
时,Clang 说:
error: incompatible pointer types passing '
struct __va_list_tag **
' to parameter of type 'va_list *
' (aka '__builtin_va_list *
')错误:不兼容的指针类型将“
struct __va_list_tag **
”传递给“va_list *
”类型的参数(又名“__builtin_va_list *
”)
This only seems to happen when taking the address of a va_list
passed as an argument, not when it's taken from a local variable.这似乎只发生在将
va_list
的地址作为参数传递时,而不是从局部变量中获取时。 Unfortunately, I do need my v
variant to take a va_list
object and not a pointer to it.不幸的是,我确实需要我的
v
变体来获取va_list
对象而不是指向它的指针。
It's easy to get consistent cross-platform value semantics for va_list
using va_copy
.使用
va_copy
很容易为va_list
获得一致的跨平台值语义。 Is there a cross-platform way to get consistent reference semantics for va_list
?有没有一种跨平台的方式来为
va_list
获得一致的引用语义?
The thing at issue here is that va_list
, on the x86 platform, is defined as an array of 1 element (let's call it __va_list_tag[1]
).这里的问题是
va_list
在 x86 平台上被定义为一个包含 1 个元素的数组(我们称之为__va_list_tag[1]
)。 It decays to a pointer when accepted as an argument, so &ap
is wildly different depending on whether ap
is a parameter of the function ( __va_list_tag**
) or a local variable ( __va_list_tag(*)[1]
).当作为参数接受时,它会衰减为指针,因此
&ap
有很大不同,具体取决于ap
是函数的参数( __va_list_tag**
)还是局部变量( __va_list_tag(*)[1]
)。
One solution that works for this case is simply to create a local va_list
, use va_copy
to populate it, and pass a pointer to this local va_list
.适用于这种情况的一种解决方案是简单地创建一个本地
va_list
,使用va_copy
来填充它,然后将一个指针传递给这个本地va_list
。 ( godbolt ) (天马行空)
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);
}
In my case, vprint_integers
and va_copy
are necessary because the interface of vprint_integers
accepts a va_list
and that cannot change.就我而言,
vprint_integers
和va_copy
是必要的,因为该接口vprint_integers
接受va_list
和不能改变。 With more flexible requirements, changing vprint_integers
to accept a va_list
pointer is fine too.对于更灵活的要求,更改
vprint_integers
以接受va_list
指针也很好。
va_list
isn't specified to be anything in particular, but it's defined to be an object type, so there's not really a reason to believe that you can't take or pass its address. va_list
没有被指定为任何特别的东西,但它被定义为一个对象类型,所以没有理由相信你不能获取或传递它的地址。 Another very similar solution that entirely bypasses the question of whether you can take the address of a va_list
is to wrap the va_list
in a struct and pass a pointer to that struct.另一个完全绕过是否可以获取
va_list
地址问题的非常相似的解决方案是将va_list
包装在一个结构中并传递一个指向该结构的指针。
You may be out of luck.你可能不走运。 7.15(3) says what you're doing has indeterminate effects.
7.15(3) 说你正在做的事情具有不确定的效果。
The object
ap
may be passed as an argument to another function;对象
ap
可以作为参数传递给另一个函数; if that function invokes theva_arg
macro with parameterap
, the value ofap
in the calling function is indeterminate and shall be passed to theva_end
macro prior to any further reference toap
.如果该函数使用参数
ap
调用va_arg
宏,则调用函数中ap
的值是不确定的,应在进一步引用ap
之前传递给va_end
宏。
The call to va_arg
in print_integer
is causing ap
in print_integers
to be indeterminate.要调用
va_arg
在print_integer
造成ap
在print_integers
是不确定的。 On one architecture it's being incremented, on another it isn't.在一种架构上它是递增的,而在另一种架构上则不是。
As for what va_list
is, it could be anything...至于
va_list
是什么,它可以是任何东西......
...which is an object type suitable for holding information needed by the macros va_start, va_arg, va_end, and va_copy.
...这是一种适合保存宏 va_start、va_arg、va_end 和 va_copy 所需信息的对象类型。
You may wish to consider a different approach to solving the underlying problem . 您可能希望考虑采用不同的方法来解决根本问题。
The problem in the second program is that if va_list
is an array type, then the function parameter ap
has its type adjusted to be a pointer type.第二个程序的问题是如果
va_list
是数组类型,那么函数参数ap
的类型被调整为指针类型。 So the argument has a different level of indirection in each case.因此,在每种情况下,该参数都有不同的间接级别。
Since C11 we can solve this with a generic selector to test whether or not ap
is still a va_list
:从 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 solution was inspired by this answer which used a macro for the two cases that required manual configuration .这个解决方案的灵感来自这个答案,它在需要手动配置的两种情况下使用了一个宏。
Notes about the _Generic
:关于
_Generic
注意事项:
&ap
because, since C18, array-to-pointer decay is performed on the first expression.&ap
,因为从 C18 开始,数组到指针的衰减是在第一个表达式上执行的。_Generic(&ap, va_list*: &ap, default: (va_list *)ap)
however this is rejected by compilers which check for constraint violations in all branches for all inputs -- although do not go on to check constraint violations in the surrounding expressions for unselected branches._Generic(&ap, va_list*: &ap, default: (va_list *)ap)
但是这被编译器拒绝,编译器检查所有输入的所有分支中的约束违规 - 尽管不要继续检查约束违规未选定分支的周围表达式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.