c函数如何接收变长参数?

时间:2017-11-02 06:49:56

标签: c macros variable-length

如果我有一个接收变长参数的函数

void print_arg_addr (int n, ...) 

我可以使用这三个宏来解析参数

va_start(ap,v)
va_arg(ap,t)
va_end(ap)  

据我了解,

va_start ap 指向第二个参数,

va_arg ap 移至下一个参数,

va_end ap 指向NULL。

所以我使用下面的代码片段来检查我的理解, 但事实证明,ap不会改变, 我希望ap每次增加4点。

void print_arg_addr (int n, ...)
{
    int i;
    int val;
    va_list vl;
    va_start(vl,n);
    for (i=0;i<n;i++)
    {
        val=va_arg(vl,int);
        printf ("ap:%p , %d\n",vl,val);
    }
    va_end(vl);
    printf ("ap:%p \n",vl);
}

int main() 
{
    print_arg_addr(5,1,2,3,4,5);
}

输出:

ap:0x7ffc62fb9890 , 1
ap:0x7ffc62fb9890 , 2
ap:0x7ffc62fb9890 , 3
ap:0x7ffc62fb9890 , 4
ap:0x7ffc62fb9890 , 5
ap:0x7ffc62fb9890 

谢谢!

1 个答案:

答案 0 :(得分:3)

va_list(与您的vl一样)是一些abstract data type,您不能将其传递给printf。它的实现对您的编译器(和processor architecture)是私有的,并且与ABIcalling conventions相关。使用所有警告和调试信息编译代码:gcc -Wall -Wextra -g。您会收到警告,并且undefined behavior因此您应该非常scared

换句话说,将va_listva_startva_end(以及所有stdarg(3) ...)视为由某些 magic 提供的编译器。这就是为什么它们是C11规范的一部分(读n1570)并且经常作为编译器内置实现。

如果您需要了解va_list和朋友的 internals (但您不应该需要),请在您的编译器中进行深入研究(并{{} 3}})。由于your ABI是数百万源代码行的GCC,因此您可能需要花费很多年时间来研究它。在你的情况下,我认为值得付出努力。

您还可以使用.s

查看生成的汇编程序代码(gcc -O -S -fverbose-asm文件)

当前的调用约定使用处理器寄存器。这就是为什么理解可变参数调用的细节很复杂。在20世纪80年代,争论被推到了机器堆栈上,当时va_start将一些指针返回到堆栈中。事情现在要复杂得多,你也不想深入研究这种复杂性。