提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
仅自学笔记
原文链接:https://blog.csdn.net/m0_64280701/article/details/127160994
原文链接:https://blog.csdn.net/qq_61635026/article/details/124384367
C/C++程序内存分配的几个区域:
相关寄存器
相关汇编命令
栈区的使用是从高地址到低地址
栈区的使用遵循先进后出,后进先出
提示:推荐使用版本较低的VS编译系统,较高版本的编译系统会优化,越高版本的编译器约不容易观察,本次使用的是2013版本的VS
以以下代码为例
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
#include
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d\n", c);
return 0;
这时看到调用堆栈这个窗口
我们可以发现main 函数被 __tmainCRTStartup() 调用
而 __tmainCRTStartup() 又被 mainCRTStartup() 调用
因此此时的栈区大约如下图所示
然后接下来我们要观察C语言代码所对应的汇编代码,在调试状态下,右击鼠标转到反汇编
以下为反汇编代码
push ebp //在栈顶开辟ebp寄存器对应的空间
mov ebp,esp //将esp的值传入ebp中(即将ebp指针移动到原本esp指向的位置)
sub esp,0E4h //将esp的内容减去0E4h(将esp移动到原esp-0E4h的位置)
push ebx //在栈顶放入ebx
push esi //在栈顶放入esi
push edi //在栈顶放入edi
此时进入main函数(也就是程序调试开始),首先要 push ebp 进行压栈,ebp 在 __tmainCRTStartup() 上面压栈
观察esp ebp 地址的变化,在调试的监视里面查看,push ebp 之后,esp 指向的位置也随之改变 (地址减小)
push ebp前
push ebp后
我们可以很明显发现esp的地址值变小了4个字节,这是因为我们在栈顶开辟ebp寄存器对应的空间,原来esp在__tmainCRTStartup()的栈顶,在上面压入一个ebp就导致esp的地址向上了一个单位,也就变小了4个字节
接下来是 mov ebp,esp ,将esp的值传入ebp中(即将ebp指针移动到esp指向的位置)
move之前
move之后
此时栈区图示(move)
接下来 sub esp,0E4h,将esp的内容减去0E4h(将esp移动到原esp-0E4h的位置,esp-0E4h地址减小)
0E4h是个16进制数,转换为10进制为228,此举相当于为main函数开辟了以一个228个字节的空间
此时图示(sub)
接下来 push ebx ,在栈顶放入ebx,地址依旧减小
接下来 push esi ,在栈顶放入 esi,地址依旧减小
接下来 push edi ,在栈顶放入edi,地址依旧减小
lea edi,[ebp-0E4h]//将ebp-0E4h的地址放入edi
mov ecx,39h//将39h放入ecx
mov eax,0CCCCCCCCh//将0CCCCCCCCh放入eax
rep stos dword ptr es:[edi]//将edi往下ecx个地址的数据全部初始化为0CCCCCCCCh(eax)
此时图示(第二部分)
int a = 10;
mov dword ptr [ebp-8],0Ah //将ebp-8的位置变成04h
int b = 20;
mov dword ptr [ebp-14h],14h //将ebp-24h得到位置变成14h
int c = 0;
mov dword ptr [ebp-20h],0 //将ebp-20h的位置变成0
此时图示
c = Add(a, b);
mov eax,dword ptr [ebp-14h]//[ebp-14h]是b=20的地址,所以赋值eax为20
push eax //压栈
mov ecx,dword ptr [ebp-8]//[ebp-8]是a=10的地址,所以赋值ecx为10
push ecx //压栈
此时图示
接下来为call 指令,按下F11,此时就正式进入Add函数内部 并为其开辟栈帧
按 F11,进入到 Add 函数 ,该add 函数地址不一定与main 函数地址相连,但是add 函数的地址一定在main 函数地址上面
call 指令调用 Add 函数,这里逐语句(F11)执行,发现这里竟然存储着下一条指令的地址,事实上 call 指令把下一条指令的地址压栈了(为了 Add 函数结束后能找回来),esp 地址也跟着变化
call 00C210E1
push ebp//将ebp上移
mov ebp,esp//将esp内容放入ebp(移动ebp)
sub esp,0CCh//esp-0CCh(为Add开辟空间)
push ebx//在栈顶放入ebx
push esi//在栈顶放入esi
push edi//在栈顶放入edi
lea edi,[ebp+FFFFFF34h]//ebp+FFFFFF34h的空间
mov ecx,33h//33存入ecx
mov eax,0CCCCCCCCh//存入eax
rep stos dword ptr es:[edi]//esp往下0ch的空间进行赋值
具体过程与mian函数的过程基本一致
此时图示
此时的ebp地址
int z = 0;
mov dword ptr [ebp-8],0
z = x + y;
mov eax,dword ptr [ebp+8] //把 ebp+8 的值 10 放到 eax 里
add eax,dword ptr [ebp+0Ch] //把 ebp+0ch 的值 20 和 eax 的值 10 相加
mov dword ptr [ebp-8],eax //把 eax 的值 30 放到 ebp-8(z) 里去
return z;
此时图示
return z;
mov eax,dword ptr [ebp-8] //把ebp-8的值(30)放到eax里头去
}
pop edi //出栈,释放为edi创建的栈区
pop esi //出栈,释放为esi创建的栈区
pop ebx //出栈,释放为exb创建的栈区//esp地址向下3个单位
mov esp,ebp //ebp的值赋给esp,此时esp和ebp相同
pop ebp //弹出ebp
ret //返回
pop edi //出栈,释放为edi创建的栈区
pop esi //出栈,释放为esi创建的栈区
pop ebx //出栈,释放为exb创建的栈区//esp地址向下3个单位
每出栈一次,esp向下一次
ebp的值赋给esp,此时esp和ebp相同,然后再弹出ebp
此时图示
ret:恢复返回地址,压入eip,类似pop eip命令,使重新回到原来的地址
此时图示
add esp,8 //是往esp里加8,即向高位移动,销毁形参
mov dword ptr [ebp-20h],eax //赋值eax(30)的值给c
printf("%d\n", c);
mov eax,dword ptr [ebp-20h]
push eax
push 0EC7B30h
call 00EC10D2
add esp,8
return 0;
xor eax,eax
add esp,8 ,而这一条指令的意思,是往esp里加8,即向高位移动,实际上这条指令就是在销毁我们的形参
此时图示
接下来就是打印值和 main函数函数栈帧销毁,都与上面类似,这里不多做赘述