1

我试图弄清楚 va_start(), va_​​arg() 宏的背后是什么。下面的代码运行良好。

#include <iostream>
#include <cstdarg>

void f(double a, double b, ...)
{
   va_list arg;
   va_start(arg, b);
   double d;
   while((d = va_arg(arg, double)) != 0)
      {
         std::cout << d << '\n';
      }
}

int main(int argc, char *argv[])
{
   f(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0);
   return 0;
}

正如我所料,它给出了这样的输出:3 4 5 6 7 8 9。然后我找到了那个宏的定义(在互联网上,因为我的头文件 stdarg.h 很神秘——它定义了宏 va_arg(v, l) 像 _builtin_va_arg( v,l),最后一个没有在其中定义,stdarg.h 不包含任何内容,所以它在某个库中???)。然而,代替“cstdarg”,我写道:

typedef char* va_list;

#define _INTSIZEOF(n) \
((sizeof(n)+sizeof(int)-1) &~(sizeof(int)-1))

#define va_start(ap,v) \
(ap = (va_list)&v + _INTSIZEOF(v))

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

#define va_end(ap) (ap = (va_list)0)

输出变得很奇怪,例如 1 -0.0409377 -0.0409377 4.88084e-270 4.85706e-270 1 2 3 4 5 6 7 8 9。我认为可变参数放在最后声明的参数旁边,但显然还有更复杂的情况。如果有人揭露我错在哪里或那里真正发生了什么,我会非常高兴。

4

2 回答 2

1

您的手写宏将在始终传递堆栈上的所有参数并将较小类型填充到 sizeof(int) 的机器上工作。然而,许多机器(现在大多数?)不会在堆栈上传递参数——而是将它们传递到寄存器中,并且只有在有太多无法放入寄存器时才使用堆栈。

因此,为了处理 va_args,编译器需要知道 ABI 以及在什么情况下将哪些参数放在哪里。通常要做的是让 va_list 包含许多数组(足以容纳所有可能包含 args 的寄存器)和许多指针(通常,每种类型的寄存器一个,堆栈一个。va_start 转储所有参数寄存器到数组中并初始化指针,然后 va_arg 计算出将传入给定参数类型的寄存器类型并将值拉出适当的位置。因此,对于具有 8 个整数/指针参数和8个用于浮点/双参数的regs,你可能会有类似的东西:

typedef struct {
    intptr_t iregs[8], *iptr;
    double   fregs[8], *fptr;
    char     *spptr;
} va_list;

inline void _builtin_va_start(va_list &ap, arg) {
    // dump the registers might be used to pass args into ap->iregs and ap-fregs,
    // setup iptr and fptr to point into iregs and fregs after the arguments that
    // correspond to 'arg' and those before it.  spptr points into the stack space
    // used for arguments after regs run out
}
inline _builtin_va_arg(va_list &ap, type) {
    if (type is integer or pointer) {
        if (ap->iptr == ap->iregs+8) {
            rv = *(type *)ap->spptr;
            ap->spptr += sizeof(type);
        } else {
            rv = *ap->iptr++;
        }
    } else if (type is float or double) {
        if (ap->fptr == ap->fregs+8) {
            rv = *(type *)ap->spptr;
            ap->spptr += sizeof(type);
        } else {
            rv = *ap->fptr++;
        }
    } else {
        // some other type (struct?) deal with it
    }
}

请注意,这些_builtin_va函数都不能用 C 编写;它们需要内置到编译器中

于 2013-03-29T23:50:18.327 回答
1

The va_start, va_arg and don't forget va_end are specific for a compiler. You can not just take them from somewhere else and expect them to work. You'd better follow the manual page in using them and only try to understand their inner workings if you are a compiler engineer.

P.S: Oh, and their definitions are usually very mysterious using subtle tricks to get it working.

P.S2: In answer to your question where _builtin_va_arg is defined: it is known by the compiler, a so called builtin. You will find it in the sources of the compiler ;)

于 2013-03-29T23:09:16.083 回答