简体   繁体   English

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

[英]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_integersva_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 the va_arg macro with parameter ap , the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap .如果该函数使用参数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_argprint_integer造成apprint_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注意事项:

  • We have to test &ap because, since C18, array-to-pointer decay is performed on the first expression.我们必须测试&ap ,因为从 C18 开始,数组到指针的衰减是在第一个表达式上执行的。
  • Originally I had _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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM