简体   繁体   English

在我的函数和类似 vprintf 的库函数之间共享 va_list

[英]Sharing va_list between my functions and vprintf-like library functions

I would like to add some special processing to formatted printing (with variadic arguments), and I found that the use of va_list s in my code does not comply with the C standard.我想对格式化打印(使用可变参数)添加一些特殊处理,我发现我的代码中使用va_list不符合 C 标准。 Although it does not cause any error when compiled with GCC, I would like it to be compliant.虽然它在使用 GCC 编译时不会导致任何错误,但我希望它是兼容的。 The following is a simplified version:以下是简化版:

#include <stdarg.h>
#include <stdio.h>

typedef struct S_ {
    int i;
    va_list *v;
} S;

void fb( S* s )
{
    int x = va_arg( *s->v, int );             // Line A
    vfprintf( stdout, "c:%c s:%s\n", *s->v ); // Line B
    int y = va_arg( *s->v, int );             // Line C
    fprintf( stdout, "d:%d\n", s->i * x * y );
}

void fa( int i, ... )
{
    S s = { i };
    va_list v;
    va_start( v, i );
    s.v = &v;
    fb( &s );
    va_end( v );
}

void main()
{
    fa( 1, 2, 'c', "string", 3 );
}

The standard says on page 249:标准在第 249 页上说:

If access to the varying arguments is desired, the called function shall declare an object (generally referred to as ap in this subclause) having type va_list.如果需要访问变化的 arguments,被调用的 function 应声明一个类型为 va_list 的 object(在本节中通常称为 ap)。 The object ap may be passed as an argument to another function; object ap 可以作为参数传递给另一个 function; 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.如果 function 调用带有参数 ap 的 va_arg 宏,则调用 function 中 ap 的值是不确定的,应在进一步引用 ap 之前传递给 va_end 宏。 (It is permitted to create a pointer to a va_list and pass that pointer to another function, in which case the original function may make further use of the original list after the other function returns.) (允许创建指向 va_list 的指针并将该指针传递给另一个 function,在这种情况下,原始 function 可以在其他 function 返回后进一步使用原始列表。)

As I understand, there is no problem if Line A is followed by Line B since the caller ( fb ) uses va_arg before invoking the callee ( vfprintf ).据我了解,如果 A 行后跟 B 行没有问题,因为调用者 ( fb ) 在调用被调用者 ( vfprintf ) 之前使用va_arg I suppose (though did not find it in the text) the reason for allowing this is that the va_list is in the scope of the caller, therefore any change done by va_arg will be reflected in the object's state when it is passed to the callee ( vfprintf ).我想(虽然没有在文本中找到它)允许这样做的原因是va_list在调用者的 scope 中,因此va_arg所做的任何更改在传递给被调用者时都会反映在对象的 state 中( vfprintf )。

However, I reckon that Line B followed by Line C is not in line with the standard since the callee ( vfprintf ) received a va_list , not a va_list* , therefore the value of *s->v is indeterminate after Line B, and should not be used as in Line C (rather it should be closed with va_end ).但是,我认为 B 行后跟 C 行不符合标准,因为被调用方( vfprintf )收到的是va_list而不是va_list* ,因此*s->v的值在 B 行之后是不确定的,应该不能像第 C 行那样使用(而是应该用va_end关闭)。 I suppose (though did not find it in the text) the reason for not allowing the use of va_list in the caller ( fb ) after the callee ( vfprintf ) returned is that *s->v may have been (depending on the implementation) copied to a local variable of vfprintf (at the function call), and in this case, since that copy is destroyed when vfprintf returns, the changes it made (consuming a char and a char* ) are not reflected in the original object, so y will not be assigned 3 but 'c' (probably in some distorted way as a char is narrower than an int ).我想(虽然没有在文本中找到它)在被调用者( vfprintf )返回后不允许在调用者( fb )中使用va_list的原因是*s->v可能已经(取决于实现)复制到 vfprintf 的局部变量(在vfprintf调用中),在这种情况下,由于该副本在vfprintf返回时被销毁,因此它所做的更改(消耗一个char和一个char* )不会反映在原始 object 中,因此y不会被分配3而是'c' (可能以某种扭曲的方式,因为charint窄)。

Unfortunatelly, I cannot make vfprintf accept va_list* but need to "mix it in" (possibly call va_arg before and after calling it with the same va_list ).不幸的是,我不能让vfprintf接受va_list*但需要“混合它”(可能在使用相同的va_arg调用它之前和之后调用va_list )。

Is my argument correct and my code incorrect?我的论点是否正确,我的代码是否不正确? If so, how can I fix it?如果是这样,我该如何解决?

Do not share: Copy:请勿分享:复制:

typedef struct S{
    int i;
    va_list v;
} S;

void fb( S* s )
{
    int x = va_arg( s->v, int );             // Line A
    vfprintf( stdout, "c:%c s:%s\n", s->v ); // Line B
    int y = va_arg( s->v, int );             // Line C
    fprintf( stdout, "d:%d\n", s->i * x * y );
    va_end(s -> v);
}

void fa( int i, ... )
{
    S s = { i };
    va_list v;
    va_start( v, i );
    va_copy(s.v, v);
    fb( &s );
    va_end( v );
}

Your reasoning is correct and your code is incorrect.你的推理是正确的,你的代码是不正确的。 Now, it might happen to work on some architectures and not work on some architectures.现在,它可能恰好在某些架构上工作而在某些架构上不工作。 For example, if an architectures implements va_list as a pointer to underlying state, then when it's passed to a function and that function updates the iteration state, that might be visible in the calling scope, and then it would work for you to continue the iteration there.例如,如果架构将va_list实现为指向底层 state 的指针,那么当它传递给 function 并且 function 更新迭代 state 时,这可能在调用 scope 中可见,然后它会为您继续迭代工作那里。 But if the architectures implements it as some kind of struct which has the iteration state by value, then when it's passed to a function and that function updates the iteration state, that will not be visible in the calling scope, so your code would not work.但是,如果体系结构将其实现为某种结构,其迭代值为 state,那么当它传递给 function 并且 function 更新迭代 state 时,在调用 scope 中将不可见,因此您的代码将无法工作. That's what happens with undefined behavior -- there is no guarantee that it works or does not work.这就是未定义行为所发生的情况——无法保证它有效或无效。

I see no general way to achieve this other than to basically re-implement vfprintf() , but take and operate on a va_list * rather than a va_list .除了基本上重新实现vfprintf()之外,我没有看到实现此目的的一般方法,而是在va_list *而不是va_list上进行操作。 Once you passed a va_list by value to a function, you cannot do va_arg on that va_list in the calling scope anymore.一旦按值将va_list传递给 function,就不能再在调用 scope 时对该va_list执行va_arg In the C standard's section on vfprintf() , it has a footnote that confirms this:在 C 标准的vfprintf()部分中,有一个脚注证实了这一点:

As the functions vfprintf, vfscanf, vprintf, vscanf, vsnprintf, vsprintf, and vsscanf invoke the va_arg macro, the value of arg after the return is indeterminate.由于函数vfprintf、vfscanf、vprintf、vscanf、vsnprintf、vsprintf和vsscanf调用了va_arg宏,返回后arg的值是不确定的。

va_copy() doesn't help here. va_copy()在这里没有帮助。 You can't va_copy() after the call to vfprintf() , because the va_list is already indeterminate at that time.您不能在调用va_copy()之后调用vfprintf() ,因为va_list当时已经不确定。 And if you va_copy() before the call to vfprintf() , the copied va_list would still be in the state before the arguments used for the vfprintf() are read.如果在调用va_copy()之前使用vfprintf() ,则在读取用于vfprintf()的 arguments 之前,复制的va_list仍将位于 state 中。 So you would need to somehow "skip over" the arguments used for the vfprintf() , in order to get to the argument after those arguments. In order to do that, you would need to know the number of arguments used for the vfprintf() and their types.因此,您需要以某种方式“跳过”用于vfprintf() ,以便在那些 arguments 之后获取参数。为此,您需要知道用于vfprintf()及其类型。 So basically you would need to re-implement the format string parsing logic of vfprintf() to do that.所以基本上你需要重新实现vfprintf()的格式字符串解析逻辑来做到这一点。 (And if you're going to do that, you might as well just re-implement vfprintf() to take a va_list * .) (如果你打算这样做,你也可以重新实现vfprintf()来获取va_list * 。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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