Bru*_*uce 23 c gcc variadic-functions calling-convention
int max(int n, ...)
Run Code Online (Sandbox Code Playgroud)
我正在使用cdecl调用约定,其中调用者在被调用者返回后清理变量.
我想知道怎么做宏va_end,va_start和va_arg工作?
调用者是否将参数数组的地址作为max的第二个参数传递?
Ski*_*izz 28
如果你看看C语言将参数存储在堆栈中的方式,宏的工作方式应该变得清晰: -
Higher memory address Last parameter
Penultimate parameter
....
Second parameter
Lower memory address First parameter
StackPointer -> Return address
Run Code Online (Sandbox Code Playgroud)
(注意,根据硬件的不同,堆栈指针可能会向下一行,而较高和较低的指针可能会被交换)
即使没有参数类型,参数总是像这样存储1....
该va_start宏只设置了一个指向第一个功能参数,例如: -
void func (int a, ...)
{
// va_start
char *p = (char *) &a + sizeof a;
}
Run Code Online (Sandbox Code Playgroud)
这p指向第二个参数.该va_arg宏做到这一点: -
void func (int a, ...)
{
// va_start
char *p = (char *) &a + sizeof a;
// va_arg
int i1 = *((int *)p);
p += sizeof (int);
// va_arg
int i2 = *((int *)p);
p += sizeof (int);
// va_arg
long i2 = *((long *)p);
p += sizeof (long);
}
Run Code Online (Sandbox Code Playgroud)
该va_end宏仅设置p值NULL.
笔记:
...参数的存在将关闭此功能并使编译器使用堆栈.当参数在堆栈上传递时,va_"函数"(它们大部分时间都是作为宏实现的)只是简单地操作私有堆栈指针.此私有堆栈指针从传递给的参数中存储va_start,然后va_arg在迭代参数时从"堆栈""弹出"参数.
假设您max使用三个参数调用该函数,如下所示:
max(a, b, c);
Run Code Online (Sandbox Code Playgroud)
在max函数内部,堆栈基本上如下所示:
+-----+
| c |
| b |
| a |
| ret |
SP -> +-----+
SP是真正的堆栈指针,它不是真的a,b而且c在堆栈上但它们的值.ret是返回地址,在函数完成时跳转到的位置.
什么va_start(ap, n)是参数的地址(n在你的函数原型中)并从中计算下一个参数的位置,所以我们得到一个新的私有堆栈指针:
+-----+
| c |
ap -> | b |
| a |
| ret |
SP -> +-----+
当你使用va_arg(ap, int)它时,返回私有堆栈指针所指向的内容,然后通过将私有堆栈指针更改为现在指向下一个参数来"弹出"它.堆栈现在看起来像这样:
+-----+
ap -> | c |
| b |
| a |
| ret |
SP -> +-----+
该描述当然是简化的,但显示了原理.