您的位置:首页 > 编程语言 > C语言/C++

浅谈函数的调用,栈帧的创建和销毁

2017-10-28 17:12 351 查看
      今天用一段简单的程序详细的看看函数在调用过程中的栈帧情况。(栈帧表示程序的函数调用记录)

      代码如下:

int Add(int a, int b)
{
int ret = 0;
ret = a + b;
return ret;
}

int main()
{
int a = 10;
int b = 20;
int ret = 0;
ret = Add(a,b);

return 0;
}
这个的函数的功能是求两数之和。

转到反汇编代码:

首先我们要明白的一点是,在程序跑起来之前,会有一个入口函数mainCRTStartup,然后该函数会调用main函数;因此,开始,我们会在内存中(栈上)为函数mainCRTStartup开辟一块空间。如图:

esp表示栈顶,ebp表示栈底,并且栈是由高地址向低地址延伸



接下来看汇编代码:

int main()
{
010F1410 push ebp
010F1411 mov ebp,esp
010F1413 sub esp,0E4h
010F1419 push ebx
010F141A push esi
010F141B push edi
010F141C lea edi,[ebp-0E4h]
010F1422 mov ecx,39h
010F1427 mov eax,0CCCCCCCCh
010F142C rep stos dword ptr es:[edi]

一步步分析:
010F1410 push ebp push进行压栈操作,将ebp压入栈中,esp指向新的栈顶,如图:



010F1411 mov ebp,espmov移动指令,将esp放入ebp中,也就是说ebp指向了esp处的位置,esp暂时不动,如图:



010F1413  sub         esp,0E4h
sub表示减指令,将esp挪向esp-0E4h的位置处,同时,这段空间就是为main函数所开辟的空间,即main函数栈帧
如图:



010F1419 push ebx
010F141A push esi
010F141B push edi
连续三次push



010F141C lea edi,[ebp-0E4h]
010F1422 mov ecx,39h
010F1427 mov eax,0CCCCCCCCh
010F142C rep stos dword ptr es:[edi]

lea表示加载有效地址,rep表示重复执行;这四行代码表示:从ebp-0E4h这个位置向ebp处循环拷贝ecx次(即39h次)eax(0cccccccch),如图:



我们可以看一下内存:



一共57次(即039h次)

继续看:

int a = 10;
010F142E mov dword ptr [ebp-8],0Ah
int b = 20;
010F1435 mov dword ptr [ebp-14h],14h
int ret = 0;
010F143C mov dword ptr [ebp-20h],0
ret = Add(a,b);
010F1443 mov eax,dword ptr [ebp-14h]
010F1446 push eax
010F1447 mov ecx,dword ptr [ebp-8]
010F144A push ecx
010F144B call 010F10E6
010F1450 add esp,8
010F1453 mov dword ptr [ebp-20h],eax

return 0;
010F1456 xor eax,eax

继续一步步分析:
int a = 10;
010F142E mov dword ptr [ebp-8],0Ah

这句代码表示:将0Ah存入ebp-8处的这个位置,我们可以看内存:
ebp的位置:



ebp-8的位置:



我们可以再看看0Ah是否存入了ebp-8处:



通过内存窗口可以看到0A已经存入ebp-8处;

再看b:

int b = 20;
010F1435 mov dword ptr [ebp-14h],14h

还是同上:



14h也成功存入;

再看ret:

int ret = 0;
010F143C mov dword ptr [ebp-20h],0

如图:



ret=0也成功存入;

继续看:

ret = Add(a,b);
010F1443 mov eax,dword ptr [ebp-14h]
010F1446 push eax
010F1447 mov ecx,dword ptr [ebp-8]
010F144A push ecx

这几句代码表示:将ebp-14h的值(也就是b的值20)存入eax中,再将他压入栈中;
                             将ebp-8h的值(也就是a的值10)存入ecx中,再将其压入栈;

注意,这就是函数传参为什么是从右向左。

如图:



继续往下看:

010F144B call 010F10E6
010F1450 add esp,8
call表示要调用函数了,此刻注意call指令的下一个地址:010F1450;

按一下F11,进入Add函数,此时,已经将010F1450这个地址压入栈中,如图:



现在进入Add函数内部:

int Add(int a, int b)
{
010F13C0 push ebp
010F13C1 mov ebp,esp
010F13C3 sub esp,0CCh
010F13C9 push ebx
010F13CA push esi
010F13CB push edi
010F13CC lea edi,[ebp+FFFFFF34h]
010F13D2 mov ecx,33h
010F13D7 mov eax,0CCCCCCCCh
010F13DC rep stos dword ptr es:[edi]
int ret = 0;
010F13DE mov dword ptr [ebp-8],0
ret = a + b;
010F13E5 mov eax,dword ptr [ebp+8]
010F13E8 add eax,dword ptr [ebp+0Ch]
010F13EB mov dword ptr [ebp-8],eax
return ret;
010F13EE mov eax,dword ptr [ebp-8]

继续分析:
010F13C0 push ebp这句代码表示:将main函数的ebp压入栈中,如图:



继续看:

010F13C1 mov ebp,esp
010F13C3 sub esp,0CCh
010F13C9 push ebx
010F13CA push esi
010F13CB push edi
010F13CC lea edi,[ebp+FFFFFF34h]
010F13D2 mov ecx,33h
010F13D7 mov eax,0CCCCCCCCh
010F13DC rep stos dword ptr es:[edi]

这段代码和第一个一样,在栈上开辟一块属于Add函数的空间:



int ret = 0;
010F13DE mov dword ptr [ebp-8],0
ret = a + b;
010F13E5 mov eax,dword ptr [ebp+8]
010F13E8 add eax,dword ptr [ebp+0Ch]
010F13EB mov dword ptr [ebp-8],eax
这段代码表示:将0存入ebp-8的位置处,然后再将ebp+8的值,也就是10存入eax中,将ebp+0ch处的值也就是20和eax中的值(10)相加并存入ebp-8的位置处(即ret值由0变为30),看图;





return ret;
010F13EE mov eax,dword ptr [ebp-8]再将ebp-8处的值,也就是30存入eax中;
到这里,Add函数已经调完。

继续往下看:

010F13F1 pop edi
010F13F2 pop esi
010F13F3 pop ebx
010F13F4 mov esp,ebp
010F13F6 pop ebp
010F13F7 ret

在这里,三次pop表示弹出栈,如图:



010F13F4 mov esp,ebp 将esp指向ebp的位置:



010F13F6 pop ebp 弹出ebp,此时,ebp回到了main函数的栈底位置处,因为ebp在弹出之前指向的位置处保存着main函数栈底的位置:



010F13F7 ret ret返回指令后,我们可以发现:



返回到了call指令的下一条指令位置处,特别注意刚刚我们前面在栈上存储的call指令的下一个指令位置:010F1450

因此,直接返回该位置处;

注意esp的位置变化:



010F144B call 010F10E6
010F1450 add esp,8
010F1453 mov dword ptr [ebp-20h],eax

return 0;
010F1456 xor eax,eax

紧接着:
010F1450 add esp,8注意esp:



两个形参(10,20)分别销毁,还给了操作系统;

再看:

v010F1453 mov dword ptr [ebp-20h],eax

刚刚我们eax中存的是30这个返回值,现在将他存入ebp-20h处(main函数中ret的位置处):



此刻,实参ret的值为30;



到此,整个程序回到main函数中,当main函数调用完后,整个程序也结束了。

这就是函数调用过程中,函数栈帧的创建和销毁情况,有错误的地方欢迎大家指正,谢谢大家!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 函数栈帧