有如下函数,其中有函数的并行调用和嵌套调用:
include
int g = 1;
double a(double x,double p){ p = x;
return x;
}
int d(int x){
return x;
}
int c(int x,int p){ p = x;
return d(3)+x;
}
int b(int x){
int a,b;
b = c(4,&a);
printf("%d\n",a);
return b+x;
}//代码效果参考:http://www.zidongmutanji.com/bxxx/393430.html
typedef struct Point_t{
double x,y;
}Point;
Point e(Point a){
return a;
}
main()
{
Point pt;
pt.x = 1.25;
pt.y = 2.75;
int s = a(1.25,&(pt.x));
s += b(5);
s+= g;
printf("%d %lf\n",s,pt.x);
e(pt);
getchar();
}
1 全局变量已在程序执行前加载(加载阶段)
2 系统调用main函数
main函数调用前EBP与ESP的初始值:
3 EBP压栈并更新,抬高esp
0x12FF48-0x78 = 12FED0
4 保存寄存器状态
(寄存器值压栈,栈顶操作,78h以外)
push时,esp抬高(址值减小);
5 栈帧(这里安排了78h个字节)的初始化(调试模式下)
6 main函数的数据在此栈帧上操作
ebp-x,也就是从栈底操作。
7 调用函数前参数压栈
(栈顶操作,78h以外)
注意传址时,压的地址还是ebp-x。也就是主调函数的局部变量地址。
8 main()调用a()
对参数的引用:ebp+x
返回double的汇编指令:
fld qword ptr [ebp+8]
函数返回,通过以下几个汇编指令,esp、ebp回退:
00401079 pop edi
0040107A pop esi
0040107B pop ebx
0040107C mov esp,ebp
0040107E pop ebp
0040107F ret
通过以下汇编,esp降到压参前的位置:
004011E4 add esp,0Ch
函数返回值类型转换,值返回:
004011E7 call __ftol (0040176c)
004011EC mov dword ptr [ebp-14h],eax
9 main()调用b()
main()调用完a(),再调用b(),a()与b()之间是并行调用关系(都由main调用),并行调用关系的函数的栈帧是相互叠加关系,后面调用的函数覆盖在前面函数的栈帧上。
10 b()调用c()
11 c()调用d()
函数嵌套调用时,栈帧上下叠加。
12 函数d()、c()、b()返回
函数d()返回后,剩下main()、b()、c()的栈帧;
函数c()返回后,剩下main()、b()的栈帧;
函数b()返回后,剩下main()的栈帧;
13 main()函数调用e()
13.1 ebp、esp初始值
esp: 0012FEC4
……
ebp: 0012FF42
图示:
pt两个分量对应的16进制值:
1.25 3F F4 00 00 00 00 00 00
2.75 40 06 00 00 00 00 00 00
13.2 压参
13.3 当返回值不是整型或浮点型时
当被调函数的返回值不是整形或浮点型的复合类型时,用寄存器保存不了,需要将返回值保存到主调函数的栈帧空间。
对应以下内存映像:
13.4 返回值保存到被调函数栈空间并赋值函数保存返回值的临时空间
14 总结
函数并行被调用:栈帧相互叠加(重叠);
函数嵌套调用:栈帧上下叠加(不重叠);
函数的递归调用也是一种嵌套调用,所以栈内存的增长会很快!
函数调用时建立栈帧,但栈帧并不只限于栈的push、pop操作esp,且也可以通过ebp进行偏移,也可以将主调函数的栈帧上的局部变量的地址通过通过参数压到栈上,被调函数通过间接寻址来访问主调函数的局部变量空间(如传址和值返回一个复合类型的值)。