如果我有一个接收变长参数的函数
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
谢谢!
答案 0 :(得分:3)
va_list
(与您的vl
一样)是一些abstract data type,您不能将其传递给printf
。它的实现对您的编译器(和processor architecture)是私有的,并且与ABI和calling conventions相关。使用所有警告和调试信息编译代码:gcc -Wall -Wextra -g
。您会收到警告,并且undefined behavior因此您应该非常scared。
换句话说,将va_list
,va_start
,va_end
(以及所有stdarg(3) ...)视为由某些 magic 提供的编译器。这就是为什么它们是C11规范的一部分(读n1570)并且经常作为编译器内置实现。
如果您需要了解va_list
和朋友的 internals (但您不应该需要),请在您的编译器中进行深入研究(并{{} 3}})。由于your ABI是数百万源代码行的GCC,因此您可能需要花费很多年时间来研究它。在你的情况下,我认为值得付出努力。
您还可以使用.s
gcc -O -S -fverbose-asm
文件)
当前的调用约定使用处理器寄存器。这就是为什么理解可变参数调用的细节很复杂。在20世纪80年代,争论被推到了机器堆栈上,当时va_start
将一些指针返回到堆栈中。事情现在要复杂得多,你也不想深入研究这种复杂性。