va_list 总是静态的吗?

Is va_list always static?

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_list'ap1'在'va_test1'中初始化后,被复制到'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 似乎无论在何处声明都是静态行为。

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

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

Is va_list always static?

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

您的代码的行为未定义。在调用 va_test2(ap1); 之后,你唯一可以用 ap1 做的事情 就是调用 va_end(ap1)。我们可以阅读 C11 7.16p3:

[...] The object ap may be passed as an argument to another 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.

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

As far as I know, va_arg is defined

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

我会说,很可能不会。如果您在 x86 体系结构上,则使用不同的寄存器传递不同的数据类型,如 x86 abi (ex. see page 21 and the whole section around page 52) (and see this answer). That definition could most probably work on a few limited cases. Nowadays, va_arg is usually some compiler magic, like gcc/stdarg.h __builtin_va_arg.

所指定

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

// 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: - 您不能 期望 来自此类代码的任何东西 - 通常期望 nasal demons to spawn.

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

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

在古老的 C 编译器中,va_list 是一个简单的指针,它被转换为指向您传递给 va_arg 宏的类型的指针,以便能够用它进行指针运算并推进它指向列表中的下一个参数。这些是与流程关联的语义。但是这个指针算法必须是特殊的,因为不同的 cpus 更新指向堆栈的指针通常以不同于普通数据指针的方式对齐数据。这意味着,例如,如果您在 64 位架构中传递一个短数字或一个 int(一个 32 位整数),则可能会将一个完整的 64 位字压入堆栈以保存单个字符(或整数),出于效率原因。不管怎样,它是一个架构最依赖的部分,这也是大多数编译器特别对待它的原因(在 gcc 中它被映射到 __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);
}