va_list总是静态的吗?

Is va_list always static?

提问人:ILoveC 提问时间:1/20/2021 最后编辑:ILoveC 更新时间:1/23/2021 访问量:282

问:

void    va_test2(va_list ap2)
{
    printf("va_test 2 : %d\n", va_arg(ap2, int));
}
void    va_test1(const char* str, ...)
{
    va_list ap1;
    printf("%s\n", str);
    va_start(ap1, str);
    va_test2(ap1);
    printf("va_test 1 : %d\n", va_arg(ap1, int));
    va_test2(ap1);
}
int     main(void)
{
    va_test1("this is a test", 1, 2, 3);
}
result :
    this is a test
    va_test 2 : 1
    va_test 1 : 2
    va_test 2 : 3
result I expected:
    this is a test
    va_test 2 : 1
    va_test 1 : 1
    va_test 2 : 2

在我看来,在“va_test1”中初始化va_list“ap1”后,它被复制到“va_test2”中的局部变量“ap2”。

因此,在 va_arg(ap, int) 在 'va_test2' 中增加va_list 'ap2' 后,应该不会影响原来的va_list 'ap1'。

但该行为表明,增加的参数实际上影响了“ap1”。

据我所知,va_arg是定义的

#define va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(T)) - _INTSIZEOF(T)))

这直接增加了发送的指针。

在我的结论中,va_list似乎在任何地方都是静态行为。

你能告诉我这是对的吗,为什么它显示静态行为?

C 参数 std

评论

2赞 Franz Gleichmann 1/20/2021
对于有关的问题,请不要标记 - 因为它是一种完全不同的语言cc#
1赞 KamilCuk 1/20/2021
va_test2(ap1);后跟其他任何内容 (on ) 则为未定义的行为。对 after 的调用是未定义的行为。ap1va_end(ap1)va_arg(ap1va_test2(ap1)
0赞 KamilCuk 1/20/2021
As far as I know, va_arg is defined你从哪里得到这个定义?你怎么知道这是真的?

答:

1赞 Eric Postpischil 1/20/2021 #1

这里没有证据表明有静态存储持续时间。只有证据可以访问 中的数据或引用的数据,这可能是因为是数组或指向数据的指针或包含此类指针的结构。ap1va_test2ap1ap1

评论

0赞 ILoveC 1/20/2021
谢谢你的回答。它肯定不是静态的。我仍在弄清楚为什么 ap2 更改了 ap1 的值。如果您知道为什么,请告诉我
2赞 KamilCuk 1/20/2021 #2

va_list总是静态的吗?

不,不是。它通常是本地的。

未定义代码的行为。通话结束后,您唯一能做的就是拨打 .我们可以阅读 C11 7.16p3va_test2(ap1);ap1va_end(ap1)

[...]对象 ap 可以作为参数传递给另一个函数;如果该函数使用参数 ap 调用 va_arg 宏,则调用函数中 AP 的值是不确定的,应在进一步引用 AP 之前传递给 va_end 宏。

不过,可以通过拒绝定义来解释代码的行为。您得到的行为与宏的显示定义不匹配,因为宏确实修改了变量的值(除非存在隐藏:),因此拒绝该定义将是前进的方式。va_arg#define ap *ap

据我所知,va_arg是定义的

#define va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(T)) - _INTSIZEOF(T)))

我会说,很可能不是。如果您使用的是 x86 架构,则使用 x86 abi 指定的不同寄存器传递不同的数据类型(例如,请参阅第 21 页和第 52 页周围的整个部分)(请参阅此答案)。这一定义很可能适用于少数有限的情况。如今,通常是一些编译器的魔术,例如 gcc/stdarg.h __builtin_va_argva_arg

如果是数组类型或指向堆栈上数据的指针,则可以解释该行为。它是一个结构的数组,如上面的 abi 中所定义。va_listx86

// cross my fingers these are right
typedef int va_list[1];
#define va_start(ap, a)   (*ap = (int*)&a);
#define va_arg(ap, t)     (*(t*)((*ap += _INTSIZEOF(T)) - _INTSIZEOF(T)))

由于代码的行为未定义,因此编译器实现者并不关心此类代码的行为方式 - 它可以以任何方式运行。因此,你不能 - 你不能期望从这样的代码中得到任何东西 - 通常期望鼻恶魔生成result I expected:

评论

0赞 ILoveC 1/21/2021
你知道所有这些事实真是太神奇了。我想我太沉迷于我在互联网上找到的宏观,根本没有考虑建筑。非常感谢您的回答。
0赞 KamilCuk 1/21/2021
哎呀,另一边的另一个例子,sdcc stdarg.h 和 pic16/stdarg.h 具有按值使用的简单方法,并且可以按预期工作。va_argva_list
0赞 Luis Colorado 1/23/2021 #3

不,它不能是静态的。原因是静态将不允许函数可重入,因此您应该不能在不同的线程中使用可调参数函数。

va_list是普通类型,如指针。唯一的区别是,这是一个非常特殊的指针,指向参数列表中的变量,事实上,根据ABI,它可能非常特殊(因为例如,如果您的ABI允许在寄存器中传递参数,它应该引用寄存器)

在古代 C 编译器中,是一个简单的指针,它被强制转换为指向传递给宏的类型的指针,以便能够使用它进行指针运算,并将其推进以指向列表中的下一个参数。这些是与过程相关的语义。但这种指针运算必须很特殊,因为指向堆栈的不同 cpus 更新指针通常以与普通数据指针不同的方式对齐数据。这意味着,例如,如果在 64 位体系结构中传递一个短数字或一个(32 位整数),出于效率原因,可能会将一个完整的 64 位字推送到堆栈以保存单个字符(或整数)。无论如何,它是一个架构最依赖的部分,这就是大多数编译器特别对待它的原因(在 gcc 中,它被映射到 )。但从本质上讲,它是一个指针(可能是通过引用传递的,你不知道)。va_listva_argint__gnuc_va_list

在 C 语言中,有一种通过引用传递变量的方法,包括将变量声明为该变量的一个元素的数组。如果你在实际的参数列表中写下数组名称,你确实是通过引用传递一个变量,因为变量名是对只有一个元素的数组的第一个元素的引用:

typedef void *va_list[1];
#define va_start(_l, _first) do{_l[0] = &(_first)+1;}while(0)
#define va_arg(_l, _typ) (*(_typ *)(_l[0] = (_typ *)_l + 1));
#define va_end(_l)  /* just nothing */

上面的代码将为您提供指针的语义,当您将指针作为参数传递给函数时,该指针将通过引用传递(因为您已经定义了类型,当您按名称传递它时,您使用的是引用)

#include <stdio.h>

/* these are implemented in a library and the user doesn't
 * see the details on how my_type is defined. */
typedef int va_list_fake[1];

void va_start_fake(va_list_fake a, int val)
{
    a[0] = val;
}

int va_arg_fake(va_list_fake a)
{
    return a[0];
}

void exchange(va_list_fake a, va_list_fake b)
{   /* this is not seen by the function user */
    int temp = a[0];
    a[0] = b[0];
    b[0] = temp;
}

void va_end_fake(va_list_fake a)
{
    /* empty */
}

/* now it comes the code in main that doesn't know how is
 * implemented the type va_list_fake */
int main()
{
    va_list_fake a, b;  /* they look as normal variables */

    va_start_fake(a, 3); /* compare this with va_start */
    va_start_fake(b, 2); /* idem. */

    /* print the contents of variables a and b with a simil
     * of function/macro va_arg() that differs from va_arg only
     * in the lack of a second type parameter. */
    printf("a = %d, b = %d\n", va_arg_fake(a), va_arg_fake(b));

    /* exchange, but pass the variables by value, so it should
     * not be updated.  8-. (but there's some hidden trick) */
    exchange(a, b);  /* this will exchange the values */

    /* now a contains the value 2 and b contains the value 3 */
    printf("a = %d, b = %d\n", va_arg_fake(a), va_arg_fake(b));

    va_end_fake(a);
    va_end_fake(b);
}