函数是c语言模块化编程方式的体现。
变参函数的定义一般长这样:
printf(const char*, ...);
scanf(const char*, ...);
其中,...是占位符,其作用是告诉编译器对所有参数做压栈处理。
数组的压栈方式是从右向左,而函数参数的压栈方式也是从右向左,因为这种方式易于处理变参函数。
举个例子:
#include
void print(int n,...)
{
int *p,i;
p=&n+1; //跳过第一个参数,递增到第二个参数
for(i=0;i<n;i++)
{
printf("%d\t",p[i]); //通过访问符递增地址,因为不会报错,所以可以访问,,,,,
}
printf("\n");
return ;
}
int main()
{
print(4,12,34,56,78);
return 0;
}
12 34 56 78
注意:使用变参函数时,如果没有给出变参函数的个数,直接给出第一个参数,那么必须约定一个参数作为结束标志。
我用的mac,输出的结果不对,而且最后一位是1,似乎是从左到右压栈的。
在C语言中,系统提供了实现变参函数的宏,用法如下:
#include
#include
void print(int n,...)
{
int arg,i;
va_list p;
va_start(p,n);
for(i=0;i<n;i++)
{
arg=va_arg(p,int);
printf("%d\t",arg);
}
printf("\n");
va_end(p);
return ;
}
int main()
{
print(3,12,34,56);
return 0;
}
12 34 56
系统对于这些宏的实现效果如下:
//定义一个char类型指针
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 )
函数指针是指向函数的指针。
和数组名是数组的首地址一样,函数名称是函数的首地址。
因此函数指针可以指向不同的函数,以调用不同的函数。
为了方便,通常函数指针会以宏定义的方式进行定义:
typedef void (*funp)(int a,int b);
void fun(int a, int b);
funp=fun;
funp(3,4);
这样函数指针funp就指向了函数fun,并通过funp传参进而调用fun。
函数指针的作用基本上就是作为接口,解耦两个模块。
函数压栈的方式是:
参数从右到左,局部变量从左到右。
函数调用时,会发生如下动作:
将栈底指针ebp入栈,此时调用函数的ebp保存起来了;
将ebp寄存器赋值esp,此时ebp寄存器指向原来栈帧的栈顶;
将esp指针减16,此时建立了新栈帧,栈底是原来栈帧的栈顶,栈顶是减16后保存了ebp;
还有一个是指令指针eip,指的是栈帧中执行到哪条指令了。
#include
void test(int a,int b,int c)
{
...
}
int main()
{
test(1,2,3);
return 0;
}
当调用test函数后,压栈状态是这样的:

从内存高地址存,首先存参数,从右向左,然后存指令寄存器eip的值,他的内容是下一条指令的地址,然后存栈底寄存器ebp的值,他的内容是调用者栈帧的栈底,然后再存数组内容,从右向左。
早期的压栈方式如上:先压栈参数,再压栈寄存器,再压栈临时变量;
新版本的压栈方式是:先压栈参数,再压栈临时变量,再压栈寄存器。
这样我们就可以通过这种方式获取:
void test()
{
int a;
printf("%p",*(&a+1));
return ;
}