主函数在调用函数过程中栈空间的使用情况

#include<stdio.h>
int Add(int a,int b)
{
    int z=0;
    z=a+b;
    return z;
}
int main()
{
    int a=3;
    int b=5;
    int ret=0;
    ret=Add(a,b);
    return 0;
}

编写一段简单的代码用来理解主函数在调用函数过程中栈空间的使用情况。
将编码转为汇编代码,我们来一步一步分析。
在开始之前,首先要知道esp和ebp的作用:
•esp表示一个指针:该指针永远指向已经使用的栈空间的栈顶。
•ebp表示一个指针:该指针永远指向已经使用的栈空间的栈底。
(栈空间的使用是从高地址向低地址使用的)
•调用mainSRTStartup()函数
在进入主函数之前,会先调用mainSRTSartup()函数。
在mainSRTStartup()函数调用完成后,栈空间的使用情况如下:
•进入main()主函数
这里写图片描述
可以看到,在进入主函数之后,并没有直接创建变量。
push ebp —>压栈(将ebp的地址存入esp所指向位置的上面4个字节内)
这里写图片描述
move ebp esp—>让ebp指向esp所指向的位置
这里写图片描述
sub 0D8h—>让esp指向esp减去0D8h(十六进制数字)的位置
这里写图片描述
push ebx(寄存器)
push esi(寄存器)
push edi(寄存器)
这里写图片描述
lea edi,[ebp-0D8h]—>把[ebp-0D8h]的地址存入edi中
mov ecx,36h—>将36h(十六进制数字)存入ecx中
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
从edi这个位置开始,重复做一件事情(重复ecx(36h)次):将eax(0CCCCCCCCh)的内容存储到edi中存储的地址([ebp-0D8h])开始的向上的位置里去。—(初始化内存空间)
这里写图片描述
•开始执行主函数语句
这里写图片描述
上图汇编代码为vs2013环境下的汇编代码,以下讲解使用vc6.0的环境下的编译代码进行讲解。

 int a=3;
 int b=5;
 int ret=0;

mov dword[ebp-4],3—>将3存入[ebp-4]所指向的内存空间内
mov dword[ebp-8],5—>将5存入[ebp-8]所指向的内存空间内
mov dword[ebp-12],0—>将0存入[ebp-12]所指向的内存空间内
这里写图片描述
•调用Add()函数
mov eax,dword ptr[ebp-8]—>将[ebp-8]中放的内容存入eax中(寄存器),此时[ebp-8]中的内容为5
push eax—>在栈顶开辟新的空间(4个字节)来存放5
mov ecx,dword ptr[ebp-4]—>将[ebp-4]中放的内容存入ecx中(寄存器),此时[ebp-4]中的内容为3
push ecx—>在栈顶开辟新的空间(4个字节)来存放3
这里写图片描述
call @ILT+5(_Add)(013B11EFh)—>函数调用
从013B1B04这个位置直接跳转到013B11EFh所指向的位置(在操作系统中,此过程被称为“现场保护”)此时main()函数并没有结束,只是停在了013B1B04这个位置,等待Add()函数执行完毕之后,main()函数会从013B1B04这个位置继续向下执行。此时在栈顶又会开辟新的空间(4字节),用来存放013B1B04这个地址。

这里写图片描述
jmp Add(013B11EFh)—>跳入Add()函数的内部
这里写图片描述
开始执行函数语句之前,Add()函数做了与main()函数一样的工作。
※所以推断出在每一次函数调用开始的时候都要为这个函数开辟一段空间(运行时堆栈/函数栈帧)。
此时的内存使用情况为:
这里写图片描述
底部绿色的ebp为main()函数的栈底地址,是因为当一个函数被调用完毕时必须回到上一个函数中去,所以就要保存上一个函数的栈底地址。
•开始执行Add()函数语句

int z=0;
z=a+b;
return z;

所对应的汇编代码如图所示:
这里写图片描述
mov dword ptr[ebp-4],0—>将0存入[ebp-4]的位置
mov eax,dword ptr[ebp+8]—>将ebp+8中的内容存入eax(eax此时存储的是b的值5)中
add eax,dword ptr[ebp+0Ch]—>将a与b的值相加并存放在eax中
mov dword ptr[ebp-4],eax—>将此刻eax的值(8)放到[ebp-4]的位置里去
mov eax,dword ptr[ebp-4]—>又把[ebp-4]中的值放到eax中去,因为当函数调用完成时,所使用的内存会返还给电脑,但是寄存器不会。
pop edi
pop esi
pop ebx
上面三句汇编代码会令esp的位置返回,此时esp指向的位置如图所示:
这里写图片描述
即图片中紫色esp所指向的位置。
mov esp,ebp—>把ebp的值赋给给esp(令esp返回)。如图所示:
这里写图片描述
pop ebp—>出栈,将原来栈中的元素放入上一个函数的ebp中,(令ebp指向的位置回到上一个函数的栈底),此时esp也会因为ebp的出栈而返回四个字节。如图所示:
这里写图片描述
即为图中紫色ebp所指向的位置。
函数执行到这一步时,图中紫色esp向上的空间(即为为Add()函数所开辟的空间)已经返还给了操作系统。
ret —>直接跳回call指令的下一条指令(是由紫色esp向下四个字节所保存的地址(013B1B04)找到call指令的下一条指令),此时esp返回四个字节,保存地址(013B1B04)的四个字节也被返还给操作系统。此时的esp和ebp所指向的位置如图所示:
这里写图片描述
即为图中紫色esp与紫色ebp所指向的位置。
接下来的汇编代码如图所示:
这里写图片描述
add esp,8—>给esp加8,让esp返回8个字节,如图所示:
这里写图片描述
易知,紫色esp以上的位置都会被返还给操作系统,所以此时为Add()函数创建的形参不可以再继续使用。
mov dword ptr[ret],eax—>将eax中的值(8)保存到ret所在的4个字节中去。
这里写图片描述
xor eax,eax—>将eax清零。

猜你喜欢

转载自blog.csdn.net/h___q/article/details/79038327