【问题标题】:Using va_list as an array in C在 C 中使用 va_list 作为数组
【发布时间】:2018-02-26 00:22:44
【问题描述】:

像数组一样读取 va_list 而不是使用 va_arg 函数是否是安全且已定义的行为?

前:

void func(int string_count, ...)
{
    va_start(valist, string_count);
    printf("First argument: %d\n", *((int*)valist));
    printf("Second argument: %d\n", *(((int*)valist)+1));
    va_end(valist);
}

同样的作业问题 例如:

void func(int string_count, ...)
{
    va_start(valist, string_count);
    printf("Third argument: %d\n", *(((int*)valist)+2));
    *((int*)valist+2)=33;
    printf("New third argument: %d\n", *(((int*)valist)+2));
    va_end(valist);
}

PS:这似乎适用于 GCC

【问题讨论】:

    标签: c arrays pointers variadic-functions undefined-behavior


    【解决方案1】:

    不,它不是,你不能假设任何事情,因为实现因库而异。 访问这些值的唯一可移植方法是使用stdarg.h 中定义的宏来访问 省略。字体的大小很重要,否则你最终会阅读车库 如果您读取的字节数多于传递的字节数,则您的行为未定义。

    所以,要获得一个值,你必须使用va_arg

    见:STDARG documentation

    您无法猜测va_list 的工作原理,或特定的 执行。 va_list 的工作方式取决于 ABI、架构、 编译器等。如果您想更深入地了解va_list,请参阅 this answer.

    编辑

    几个小时前,我写了this answer 解释如何使用 va_*-宏。看看那个。

    【讨论】:

      【解决方案2】:

      不,这不安全且定义明确。 va_list 结构可以是任何东西(您假设它是指向第一个参数的指针),并且参数可能会或可能不会以“正确顺序”连续存储在指向的某些内存区域中。

      Example of va_list implementation that doesn't work for your code - 在此设置中,一些参数在寄存器而不是堆栈中传递,但 va_arg 仍然必须找到它们。

      【讨论】:

      • 如果我这样做呢? int *firstargument_ptr=&va_arg(valist, int); int alignment = ((int*)&va_arg(valist, int)) - firstargument_ptr 然后我通过 *(firstargument_ptr+i*alignment) 得到后续参数,在某些编译器上它是否仍然未定义?
      • @AlexSim 您只能使用STDARG documentation 中解释的宏。 va_list 不是指向第一个非命名参数的指针,忘记这一点。
      • @Pablo 在我的评论中我没有使用 va_list,我使用 va_arg 获取第一个参数的地址,并使用第二个 va_arg 获取两个参数之间的对齐
      • @AlexSim 仍然是错误的,因为您假设的事情根本不正确。见softwareengineering.stackexchange.com/a/249923
      • @Pablo 明白了,所以当我必须多次阅读它时(例如:排序元素),我无法避免使用动态分配,可以吗?
      【解决方案3】:

      如果一个实现的文档指定 va_list 可以以超出标准中给出的方式使用,您可以在该实现上以这种方式使用它们。即使在指定了参数布局的平台上,尝试以其他方式使用参数也可能会产生不可预知的后果。例如,在可变参数以相反顺序压入堆栈的平台上,如果要执行以下操作:

      int test(int x, ...)
      {
        if (!x)
          return *(int*)(4+(uintptr_t)&x); // Address of first argument after x
        ... some other code using va_list.
      }
      int test2(void)
      {
        return test(0, someComplicatedComputation);
      }
      

      处理 test2 的编译器可能会查看 test 的定义, 请注意,它(显然)在第一个 参数为零,因此得出结论它不需要计算和 传递 someComplicatedComputation 的结果。即使文档 对于平台文档可变参数的布局,事实是 编译器看不到它们被访问可能会导致它得出结论 他们不是。

      【讨论】:

        猜你喜欢
        • 2021-01-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-08
        • 1970-01-01
        • 2015-09-23
        相关资源
        最近更新 更多