函数调用是程序设计中的重要环节,也是程序员应聘时常被问及的,本文就函数调用的过程进行分析。
一、堆和栈
首先要清楚的是程序对内存的使用分为以下几个区:
l 栈区(stack):由编译器自动分配和释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。
l 堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。与数据结构中的堆是两码事,分配方式类似于链表。
l 全局区(static):全局变量和静态变量存放在此。
l 文字常量区:常量字符串放在此,程序结束后由系统释放。
l 程序代码区:存放函数体的二进制代码。
典型的内存区域分配如图所示:
其次是堆和栈的申请方式:
栈由系统自动分配,速度较快,在windows下栈是向低地址扩展的数据结构,是一块连续的内存区域,大小是2MB。
堆需要程序员自己申请,并指明大小,速度比较慢。在C中用malloc,C++中用new。另外,堆是向高地址扩展的数据结构,是不连续的内存区域,堆的大小受限于计算机的虚拟内存。因此堆空间获取和使用比较灵活,可用空间较大。
二、栈帧结构和函数调用过程
栈在函数调用中的作用:参数传递、局部变量分配、保存调用的返回地址、保存寄存器以供恢复。
栈帧(stack Frame):一次函数调用包括将数据和控制从代码的一个部分传递到另外一个部分,栈帧与某个过程调用一一映射。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低址地)。
函数调用规则:
l _cdecl:按从右至左的顺序压参数入栈,由调用者把参数弹出栈。由于每次函数调用都要由编译器产生清楚堆栈的代码,所以使用_cdecl的代码比使用_stdcall的代码要大很多,但是这种方式支持可变参数。对于C函数,名字修饰约定为在函数名前加下划线。对于C++,除非特变使用extern C,C++使用不同的名字修饰方式。
l _stdcall:按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数。
l _fastcall:主要特点就是快,因为它是通过寄存器来传送参数的,和__stdcall很象,唯一差别就是头两个参数通过寄存器传送。注意通过寄存器传送的两个参数是从左向右的,即第一个参数进ECX,第2个进EDX,其他参数是从右向左的入stack。返回仍然通过EAX。 最后,以一个例子来解释函数调用过程
void func(int param1 ,int param2,int param3) { int var1 = param1; int var2 = param2; int var3 = param3; printf("param1地址:0X%08X/n",¶m1); printf("param2地址:0X%08X/n",¶m2); printf("param3地址:0X%08X/n",¶m3); printf("var1地址: 0X%08X/n",&var1); printf("var2地址: 0X%08X/n",&var2); printf("var3地址: 0X%08X/n",&var3); } int main(int argc, char* argv[]) { func(1,2,3); return 0; }
运行结果如图:
下面分析调用过程:
在堆栈中变量分布是从高地址到低地址分布,EBP是指向栈底的指针,在过程调用中不变,又称为帧指针。ESP指向栈顶,程序执行时移动,ESP减小分配空间,ESP增大释放空间,ESP又称为栈指针。3个参数以从左向右的顺序压入堆栈,及从param3到param1,栈内分布如下图:
然后是返回地址入栈:此时的栈内分布如下:
通过跳转指令进入函数后,函数地址入栈后,EBP入栈,然后把当前ESP的值给EBP,汇编指令如下:
push ebp
mov ebp esp
此时栈顶和栈底指向同一位置,栈内分布如下:
然后是 int var1 = param1; int var2 = param2; int var3 = param3;按申明顺序依次存储。
转:http://blog.csdn.net/zhongguoren666/article/details/7586074