【问题标题】:Is va_list always static?va_list 总是静态的吗?
【发布时间】:2021-01-20 07:30:09
【问题描述】:
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 似乎是静态行为,无论它在哪里声明。

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

【问题讨论】:

  • 关于c的问题,请不要标记 c# - 因为它是一种完全不同的语言
  • va_test2(ap1); 后跟 anything else(在 ap1 上)然后 va_end(ap1) 是未定义的行为。在va_test2(ap1) 之后对va_arg(ap1 的调用是未定义的行为。
  • As far as I know, va_arg is defined 你从哪里得到这个定义?你怎么知道这是真的?

标签: c arguments std


【解决方案1】:

va_list 是否总是静态的?

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

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

[...] 对象 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 页周围的整个部分)(参见 this answer)。该定义很可能适用于一些有限的情况。如今,va_arg 通常是一些编译器魔法,比如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

【讨论】:

  • 你知道所有这些事实真是太神奇了。我想我对在互联网上找到的宏太着迷了,根本没有考虑架构。非常感谢您的回答。
  • Och,另一个例子,sdcc stdarg.hpic16/stdarg.h 有一个简单的 va_arg,它使用 va_list 的值,可以按你的预期工作。
【解决方案2】:

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

【讨论】:

  • 感谢您的回答。它肯定不是静态的。我还在弄清楚为什么 ap2 改变了 ap1 的值。如果你知道为什么,请告诉我
【解决方案3】:

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

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

在古代 C 编译器中,va_list 是一个简单的指针,它被转换为指向您传递给 va_arg 宏的类型的指针,以便能够对其进行指针运算并将其推进以指向下一个列表中的参数。这些是与流程相关的语义。但是这个指针算法必须是特殊的,因为不同的cpu更新指向堆栈的指针通常以不同于普通数据指针的方式对齐数据。这意味着,例如,如果您在 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);
}

【讨论】:

    猜你喜欢
    • 2011-06-23
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    • 2013-04-21
    • 1970-01-01
    相关资源
    最近更新 更多