✏ 1、可变长参数
C语言支持可变长参数,正常情况下C的函数参数入栈规则为__stdcall
,它是从右到左的,即函数中的最右边的参数最先入栈。例如,对于函数:
void func(int a, char b, int c, double d, int e) {
int f = 0;
printf("&a = 0x%p\n", &a);
printf("&b = 0x%p\n", &b);
printf("&c = 0x%p\n", &c);
printf("&d = 0x%p\n", &d);
printf("&e = 0x%p\n", &e);
printf("&f = 0x%p\n", &f);
}
// 输出
&a = 0x0058F968
&b = 0x0058F96C // 4 字对齐
&c = 0x0058F970 // 4
&d = 0x0058F974 // 4
&e = 0x0058F97C // 8
&f = 0x0058F954
用户栈是从上往下生长的,先占用高地址的空间,再占用低地址空间。对于在32位系统的多数编译器,每个栈单元的大小都是sizeof(int)
,而函数的每个参数都至少要占一个栈单元大小。对于固定参数列表的函数,每个参数的名称、类型都是直接可见的,他们的地址也都是可以直接得到的。
按照C标准的说明,支持变长参数的函数在原型声明中,必须有至少一个最左固定参数(这一点与传统C有区别,传统C允许不带任何固定参数的纯变长参数函数),这样我们可以得到其中固定参数的地址,根据上面的参数入栈顺序,我们可尝试写一个可变长参数的函数:
void var_args_func(const char* fmt, ...)
{
char* ap;
ap = ((char*)&fmt) + sizeof(fmt);
printf("%d\n", *(int*)ap);
ap = ap + sizeof(int);
printf("%d\n", *(int*)ap);
ap = ap + sizeof(int);
printf("%s\n", *((char**)ap));
}
int main()
{
var_args_func("%d %d %s\n", 4, 5, "hello world");
return 0;
}
解释:用ap
获取第一个变参的地址,我们知道第一个变参是4,一个int 型,所以我们用(int)ap
以告诉编译器,以ap
为首地址的那块内存我们要将之视为一个整型来使用,(int)ap
获得该参数的值;接下来的变参是5,又一个int型,其地址是ap + sizeof(第一个变参)
,也就是ap + sizeof(int)
,同样我们使用(int)ap
获得该参数的值;最后的一个参数是一个字符串,也就是char
,与前两个int
型参数不同的是,经过ap + sizeof(int)
后,