目录
压栈 push :给栈顶放一个元素
出栈 pop :从栈顶取出一个元素
寄存器:电脑中的任何指令都是在CPU上的运行的,但是CPU本身只负责运算不负责存储,数据一般都是存储在内存和寄存器中。常见的寄存器有:eax , ebx , ecx , edx ; ebp , esp
ebp , esp : 这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。
注:在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。这里使用的环境是vs2013(汇编语言)
注:在vs2013中,main函数也是被其他函数调用的。调用顺序是:
mainCRTStartup -> __tmainCRTStartup -> main
接下来会以下面这段代码为例,以汇编语言的方式逐句去分析上面的问题。
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
汇编语言:
int main()
{
00B11410 push ebp
00B11411 mov ebp,esp
00B11413 sub esp,0E4h
00B11419 push ebx
00B1141A push esi
00B1141B push edi
00B1141C lea edi,[ebp+FFFFFF1Ch]
00B11422 mov ecx,39h
00B11427 mov eax,0CCCCCCCCh
00B1142C rep stos dword ptr es:[edi]
int a = 10;
00B1142E mov dword ptr [ebp-8],0Ah
int b = 20;
00B11435 mov dword ptr [ebp-14h],14h
int c = Add(a, b);
00B1143C mov eax,dword ptr [ebp-14h]
00B1143F push eax
00B11440 mov ecx,dword ptr [ebp-8]
00B11443 push ecx
00B11444 call 00B110E1
00B11449 add esp,8
00B1144C mov dword ptr [ebp-20h],eax
printf("%d\n", c);
00B1144F mov esi,esp
00B11451 mov eax,dword ptr [ebp-20h]
00B11454 push eax
00B11455 push 0B15858h
00B1145A call dword ptr ds:[00B19114h]
00B11460 add esp,8
00B11463 cmp esi,esp
00B11465 call 00B1113B
return 0;
00B1146A xor eax,eax
}
00B1146C pop edi
00B1146D pop esi
00B1146E pop ebx
00B1146F add esp,0E4h
00B11475 cmp ebp,esp
00B11477 call 00B1113B
00B1147C mov esp,ebp
00B1147E pop ebp
00B1147F ret
#include<stdio.h>
int Add(int x, int y)
{
00B113C0 push ebp
00B113C1 mov ebp,esp
00B113C3 sub esp,0CCh
00B113C9 push ebx
00B113CA push esi
00B113CB push edi
00B113CC lea edi,[ebp+FFFFFF34h]
00B113D2 mov ecx,33h
00B113D7 mov eax,0CCCCCCCCh
00B113DC rep stos dword ptr es:[edi]
int z = 0;
00B113DE mov dword ptr [ebp-8],0
z = x + y;
00B113E5 mov eax,dword ptr [ebp+8]
00B113E8 add eax,dword ptr [ebp+0Ch]
00B113EB mov dword ptr [ebp-8],eax
return z;
00B113EE mov eax,dword ptr [ebp-8]
}
00B113F1 pop edi
00B113F2 pop esi
00B113F3 pop ebx
00B113F4 mov esp,ebp
00B113F6 pop ebp
00B113F7 ret
int main()
{
00B11410 push ebp // 指令:将ebp的值压栈到 __tmainCRTStartup 函数栈帧空间上
//这时esp会向上移动,因为是栈顶指针,栈顶指针一直指向的是栈顶,每次进
//行压栈,esp都会指向栈的最上方
//注:地址是由高到低的,所以这里esp的值是减少,因为是整型,所以这里减
//少了4个字节。
//在内存中可以看出来ebp的值被压进去了,倒着读就是 00 af f8 f4
00B11411 mov ebp,esp //指令:把 ebp 移动到 esp
//注:mov:移动。
00B11413 sub esp,0E4h //指令: 给esp减去0E4H,
//sub:减去
//0E4H是八进制数字 == 0x000000e4 == 228(十进制)
注:这里相当于给main函数预分配栈帧空间
00B11419 push ebx // 指令:将ebx的值压栈到 main 函数栈帧空间上
00B1141A push esi // 指令:将esi的值压栈到 main 函数栈帧空间上
00B1141B push edi // 指令:将edi的值压栈到 main 函数栈帧空间上
00B1141C lea edi,[ebp+FFFFFF1Ch] //指令:把 [ebp+FFFFFF1Ch] 加载到 edi
//[ebp+FFFFFF1Ch] == [ebp-0E4h]
//lea:load effecitve address 加载有效地址
00B11422 mov ecx,39h // 指令:把ecx置成39h
00B11427 mov eax,0CCCCCCCCh //指令:把eax置成0CCCCCCCCh
00B1142C rep stos dword ptr es:[edi] //指令:从 edi 开始,向下的 ecx 的空间数据全部置成
//eax
//dword : double word == 4个字节
int a = 10;
00B1142E mov dword ptr [ebp-8],0Ah //指令:把 0Ah 放进 ebp-8 的位置
//0Ah (八进制) == 0x0000000a(十六进制)
//== 10(十进制)
int b = 20;
00B11435 mov dword ptr [ebp-14h],14h //指令:同上
int c = Add(a, b);
00B1143C mov eax,dword ptr [ebp-14h] //指令:把 ebp-14h 的值放进 eax
00B1143F push eax //指令:将 eax 的值压栈到 main 函数栈帧空间上
00B11440 mov ecx,dword ptr [ebp-8] // 指令:把 ebp-8 的值放进 ecx
00B11443 push ecx //指令:将 ecx 的值压栈到 main 函数栈帧空间上
注:这里相当于传参
00B11444 call 00B110E1 //指令:call指令是把call指令下一条指令的地址压栈到main函数栈帧
//空间上
注:这里的作用是调用完成Add函数后,跳回到call指令的下一条指令
int Add(int x, int y)
{
00B113C0 push ebp //这里是main函数的ebp
00B113C1 mov ebp,esp
00B113C3 sub esp,0CCh
00B113C9 push ebx
00B113CA push esi
00B113CB push edi
00B113CC lea edi,[ebp+FFFFFF34h]
00B113D2 mov ecx,33h
00B113D7 mov eax,0CCCCCCCCh
00B113DC rep stos dword ptr es:[edi] //同main函数创建栈帧空间相同
int z = 0;
00B113DE mov dword ptr [ebp-8],0 //指令:把 0 放进 ebp-8
z = x + y;
00B113E5 mov eax,dword ptr [ebp+8] //指令:ebp+8 的值放进 eax
00B113E8 add eax,dword ptr [ebp+0Ch] //指令:ebp+0Ch 的值 加上 eax
00B113EB mov dword ptr [ebp-8],eax //指令:eax 的值放进 ebp-8
注:这里的x,y是找到之前压栈进来的值进行计算,所以说形参是实参的一份临时拷贝
return z;
00B113EE mov eax,dword ptr [ebp-8] //指令:把 ebp - 8 的值放进 eax
问题:局部变量 z 出作用域销毁,为什么在这里能把返回值传给main函数?
答:因为这里的值传给了寄存器,寄存器出程序时不会销毁
5.Add函数栈帧空间的出栈
0B113F1 pop edi //弹出
00B113F2 pop esi //弹出
00B113F3 pop ebx //弹出
00B113F4 mov esp,ebp //指令:把 ebp 赋值给 esp
00B113F6 pop ebp //指令:弹出
这里弹出 ebp 之后,ebp 回到之前指向main函数的栈底
00B113F7 ret //指令:ret 返回的时候,从栈顶弹出了 call指令下一条指令的地址 ,然后跳到那个位置
00B11449 add esp,8 //指令:esp + 8
注:这里形参其实就已经销毁了
00B1144C mov dword ptr [ebp-20h],eax //指令:把 eax 的值放进 ebp-20h
这里函数的栈帧的创建与销毁就已经解释清楚了,具体需要结合图片一步步跟随汇编语言即可
①局部变量是怎样创建的?
局部变量的创建首先为函数分配好栈帧空间,然后初始化一部分空间,然后在这个栈帧中给局部变量分配空间。
②为什么局部变量的值是随机值?
因为函数栈帧在初始化的时候会给栈帧空间随机赋值
③函数是怎样传参?传参的顺序是怎样的?
从右向左开始压栈压进去,通过指针的方式找到形参
④形参和实参是什么关系?
形参是在压栈时候开辟的空间,它和实参只是值是相同的,空间是独立的, 所以形参是实参的 一份临时拷贝,改变形参不会影响实参
⑤函数调用是怎样实现的?
⑥函数调用结束后是怎样返回的?
call指令下一条指令的地址被压进栈帧中,弹出的时候会正常返回到上一个函数
返回值是通过寄存器的方式带回的
注:以上仅代表个人记录与分享,如有错误欢迎指正