简体   繁体   English

变异函数混乱

[英]c variadic functions confusion

I'm trying to figure out what's behind va_start(), va_arg() macroses. 我试图找出va_start()和va_arg()宏背后的含义。 The code below works well. 下面的代码运行良好。

#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;
}

As I was expected it gave such output: 3 4 5 6 7 8 9. Then I found definitions of that macroses (in internet, cause my header stdarg.h is mysterious -- it defines macros va_arg(v, l) like _builtin_va_arg(v, l), the last doesn't defined in it and stdarg.h doesn't include anything, so is it in some library??? ). 正如我所期望的那样,它提供了这样的输出:3 4 5 6 7 8 9.然后我发现了这些宏的定义(在互联网上,因为我的标头stdarg.h很神秘-它像_builtin_va_arg(一样定义了宏va_arg(v,l))。 v,l),最后一个未在其中定义,stdarg.h不包含任何内容,因此在某些库中也是如此? Nevertheless, in place of "cstdarg" I wrote: 不过,我写了“ 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)

The output became wierd, such as 1 -0.0409377 -0.0409377 4.88084e-270 4.85706e-270 1 2 3 4 5 6 7 8 9. I thought that variadic parameters are placed just next to last declared parameter, but apparently there is more intricated situation. 输出变得更奇怪,例如1 -0.0409377 -0.0409377 4.88084e-270 4.85706e-270 1 2 3 4 5 6 7 8 9.我认为可变参数被放置在最后声明的参数的旁边,但是显然复杂得多情况。 I'd be very pleased if somebody unveil where am I wrong or what's really happens there. 如果有人揭露我错了地方或那里到底发生了什么,我会感到非常高兴。

The va_start , va_arg and don't forget va_end are specific for a compiler. va_startva_arg和别忘了va_end是特定于编译器的。 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. 在使用它们时,最好遵循手册页,并且只有当您是编译器工程师时,才尝试了解它们的内部工作原理。

PS: Oh, and their definitions are usually very mysterious using subtle tricks to get it working. PS:哦,他们的定义通常很神秘,使用一些细微的技巧使其起作用。

P.S2: In answer to your question where _builtin_va_arg is defined: it is known by the compiler, a so called builtin . P.S2:在回答您定义_builtin_va_arg的问题时,它是编译器已知的,即所谓的Builtin You will find it in the sources of the compiler ;) 您将在编译器的源代码中找到它;)

Your handwritten macros would work on a machine that always passes all arguments on the stack and pads up smaller types to sizeof(int). 您的手写宏将在始终传递堆栈上所有参数并将较小类型填充到sizeof(int)的计算机上工作。 However, many machines (most these days?) don't pass arguments on the stack -- they instead pass them in registers and only use the stack if there are too many to fit in registers. 但是,许多机器(最近的日子?)没有在堆栈上传递参数-而是在寄存器中传递参数,并且仅在寄存器中容纳太多参数时才使用堆栈。

So in order to deal with va_args, the compiler needs to know the ABI and which arguments will be placed where in what circumstances. 因此,为了处理va_args,编译器需要知道ABI,以及在什么情况下将哪些参数放在何处。 What is generally done is to have a va_list contain a number of arrays (enough to hold all the registers that might contain args) and a number of pointers (generally, one for each type of register and one for the stack. va_start dumps all the argument registers into the arrays and initializes the pointers, and va_arg then figures out what kind of register the given argument type would be passed in and pulls the value out of appropriate spot. So for a hypothetical processor with 8 regs for integer/pointer args and 8 regs for float/double args, you might have something like: 通常要做的是让va_list包含多个数组(足以容纳可能包含args的所有寄存器)和多个指针(通常,每种类型的寄存器一个,而堆栈的指针)。va_start转储所有参数注册到数组中并初始化指针,然后va_arg找出给定参数类型将传入哪种寄存器,并将值拉出适当的位置,因此对于一个具有8个reg的整数/指针args的假设处理器,对于float / double args的8条规则,您可能会有类似以下内容:

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
    }
}

Notice that neither of these _builtin_va functions can be written in C; 注意,这些_builtin_va函数都不_builtin_va C编写; they need to be built in to the compiler 它们需要内置到编译器中

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM