您的位置:首页 > 其它

函数的调用过程,栈桢的创建和销毁

2017-04-20 14:35 405 查看
这边文章呢,主要是从栈空间的层面深入了解C语言函数调用的过程。

下面我用一个简单的程序说明:

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

在上面这个程序里呢,主函数main里定义了3个局部变量,然后调用同文件里的Add()函数。3个局部变量毫无疑问都在栈空间上存放,当程序运行起来后我们逐步了解一下main函数里是如何基于栈实现了对Add()的调用过程,而Add()又是怎么返回到main函数里的。

我们调试这个程序,打开反汇编:

下面时main函数的汇编代码:

--- d:\baidunetdiskdownload\vs2013\4-15\4-15\test.c ----------------------------
int main()
{
011B1410 55                   push        ebp
011B1411 8B EC                mov         ebp,esp
011B1413 81 EC E4 00 00 00    sub         esp,0E4h
011B1419 53                   push        ebx
011B141A 56                   push        esi
011B141B 57                   push        edi
011B141C 8D BD 1C FF FF FF    lea         edi,[ebp+FFFFFF1Ch]
011B1422 B9 39 00 00 00       mov         ecx,39h
011B1427 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
011B142C F3 AB                rep stos    dword ptr es:[edi]
int a = 2;
011B142E C7 45 F8 02 00 00 00 mov         dword ptr [ebp-8],2
int b = 3;
011B1435 C7 45 EC 03 00 00 00 mov         dword ptr [ebp-14h],3
int ret = 0;
011B143C C7 45 E0 00 00 00 00 mov         dword ptr [ebp-20h],0
ret = Add(a, b);
011B1443 8B 45 EC             mov         eax,dword ptr [ebp-14h]
011B1446 50                   push        eax
011B1447 8B 4D F8             mov         ecx,dword ptr [ebp-8]
011B144A 51                   push        ecx
011B144B E8 91 FC FF FF       call        011B10E1
011B1450 83 C4 08             add         esp,8
011B1453 89 45 E0             mov         dword ptr [ebp-20h],eax
return 0;
011B1456 33 C0                xor         eax,eax
}
011B1458 5F                   pop         edi
011B1459 5E                   pop         esi
011B145A 5B                   pop         ebx
011B145B 81 C4 E4 00 00 00    add         esp,0E4h
011B1461 3B EC                cmp         ebp,esp
011B1463 E8 D3 FC FF FF       call        011B113B
011B1468 8B E5                mov         esp,ebp
011B146A 5D                   pop         ebp
011B146B C3                   ret


Add函数的汇编代码:

--- d:\baidunetdiskdownload\vs2013\4-15\4-15\test.c ----------------------------
#include <stdio.h>
int Add(int x, int y)
{
011B13C0 55                   push        ebp
011B13C1 8B EC                mov         ebp,esp
011B13C3 81 EC CC 00 00 00    sub         esp,0CCh
011B13C9 53                   push        ebx
011B13CA 56                   push        esi
011B13CB 57                   push        edi
011B13CC 8D BD 34 FF FF FF    lea         edi,[ebp+FFFFFF34h]
011B13D2 B9 33 00 00 00       mov         ecx,33h
011B13D7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
011B13DC F3 AB                rep stos    dword ptr es:[edi]
int sum = 0;
011B13DE C7 45 F8 00 00 00 00 mov         dword ptr [ebp-8],0
sum = x + y;
011B13E5 8B 45 08             mov         eax,dword ptr [ebp+8]
011B13E8 03 45 0C             add         eax,dword ptr [ebp+0Ch]
011B13EB 89 45 F8             mov         dword ptr [ebp-8],eax
return sum;
011B13EE 8B 45 F8             mov         eax,dword ptr [ebp-8]
}
011B13F1 5F                   pop         edi
011B13F2 5E                   pop         esi
011B13F3 5B                   pop         ebx
011B13F4 8B E5                mov         esp,ebp
011B13F6 5D                   pop         ebp
011B13F7 C3                   ret


首先我们要先了解:

ebp:栈底指针

esp:栈顶指针

每一个函数的调用都要开辟一块空间由edp和esp维护

而且ebp和esp维护的是调用main函数的mainCRTStartup函数的空间。



紧接着我们进入main函数根据汇编代码一步一步的来了解函数的调用过程以及栈桢的创建和销毁。

011B1410 55 push ebp

push就是压栈,这句话意思是把ebp压到栈顶,esp指向栈顶。效果如下所示:



011B1411 8B EC mov ebp,esp
这句话的意思是把esp的值给ebp,也就是说ebp指向esp指向的位置。效果如下图所示:



011B1413 81 EC E4 00 00 00 sub esp,0E4h
这句话的意思esp减去0E4h,栈空间由高地址指向低地址。在这里其实就是向上开辟了04Eh的空间,这块空间就是为main函数开辟的,esp此时指向栈顶。效果如下图所示:



011B13C9 53 push ebx
011B13CA 56 push esi
011B13CB 57 push edi
这三行还是压栈,就是依次把ebx、esi、edi压进去,esp指向栈顶。效果如下图所示:



011B141C 8D BD 1C FF FF FF lea edi,[ebp-0E4h]
//lea就是加载的意思,ebp-0E4h就是指刚才开辟的那一段main函数空间。这一行的意思就是说把为main函数开辟的空间加载到edi里面
011B1422 B9 39 00 00 00 mov ecx,39h
//把39h给eax
011B1427 B8 CC CC CC CC mov eax,0CCCCCCCCh
//把0CCCCCCCCh给mov
011B142C F3 AB rep stos dword ptr es:[edi]
//刚才不是把为main函数开辟的空间加载到edi里面了嘛,把这个空间里重复拷贝内容,拷贝内容eax:0CCCCCCCCh,拷贝ecx:39h次。

也就是说把这一段空间地址全部初始化为0cccccccch。效果如下图所示:



当这四行运行完了以后我们可以查看内存,从ebp-0e4h往下的57行(39h就是57)全被初始化成为0cccccccch。



int a = 2;
011B142E C7 45 F8 02 00 00 00 mov dword ptr [ebp-8],2
int b = 3;
011B1435 C7 45 EC 03 00 00 00 mov dword ptr [ebp-14h],3
int ret = 0;
011B143C C7 45 E0 00 00 00 00 mov dword ptr [ebp-20h],0

这四行汇编代码就开始创建我们的局部变量了。我们在这里把代码里的a选中,右键选择显示符号名,把对勾去掉就可以看到这样的结果。这四行代码效果如下图所示:



在看我们内存中:



我们都知道局部变量存放在我们的栈区,所以我们把现在开辟的main函数的空间叫main函数的栈桢。



紧接着我们在接着往下看汇编代码:

ret = Add(a, b);
011B1443 8B 45 EC mov eax,dword ptr [ebp-14h]
//把ebp-14h(b)的值放到eax里,esp指向栈顶;
011B1446 50 push eax
//把eax压到栈顶
011B1447 8B 4D F8 mov ecx,dword ptr [ebp-8]
//把ebp-8(a)的值放到eax里;esp指向栈顶;
011B144A 51 push ecx
//把ecx压到栈顶;
011B144B E8 91 FC FF FF call _Add (011B10E1h)
//call指令,调用函数;这个地方是最关键的地方
//在这里我们按F11进入函数会跳转到如下这样一条语句:011B10E1 E9 DA 02 00 00 jmp Add (011B13C0h);
//我们还会发现内存里在2的上面又压进去一个地址,这个地址就是我们下面这一行汇编代码开头的地址(函数调用完返回值就要使用这个地址)。如下图所示。
011B1450 83 C4 08 add esp,8
//效果如图所示:





然后我们F11进入Add函数:011B13C0 55 push ebp
//这里压进去的其实是main函数的edp
011B13C1 8B EC mov ebp,esp
011B13C3 81 EC CC 00 00 00 sub esp,0CCh
011B13C9 53 push ebx
011B13CA 56 push esi
011B13CB 57 push edi
011B13CC 8D BD 34 FF FF FF lea edi,[ebp-0CCh]
011B13D2 B9 33 00 00 00 mov ecx,33h
011B13D7 B8 CC CC CC CC mov eax,0CCCCCCCCh
011B13DC F3 AB rep stos dword ptr es:[edi]

这里的汇编代码和之前的基本上道理是一致的,执行完效果图如下所示:





int sum = 0;
011B13DE C7 45 F8 00 00 00 00 mov dword ptr [sum],0
sum = x + y;
011B13E5 8B 45 08 mov eax,dword ptr [ebp+8]
//ebp+8,此时ebp+8指向形参a;
011B13E8 03 45 0C add eax,dword ptr [ebp+0Ch]
//ebp+0ch,指向形参b,把a+b放到eax里
011B13EB 89 45 F8 mov dword ptr [ebp-8],eax
//ebp-8就是sum所在的位置,把eax(a+b)的值给sum,sum=5;
return sum;
011B13EE 8B 45 F8 mov eax,dword ptr [ebp-8]
//把sum的值再放到eax里,那么eax里存放的就是我们的返回值

效果图如下所示:



011B13F1 5F pop edi
011B13F2 5E pop esi
011B13F3 5B pop ebx
//pop就是出栈的意思,esp此时指向ebx下面的空间,这三个地址相当于被回收了
011B13F4 8B E5 mov esp,ebp
//把ebp的值给esp
011B13F6 5D pop ebp
//ebp就是我们所存储的main函数的ebp,那么此时ebp指向main函数里面的ebp
011B13F7 C3 ret
//ret指令要返回值,首先把栈顶call执行下一条指令的地址出栈,然后紧接着跳到下面这一行的地址,
这也是之前为什么要把这个地址保存,就起到了一个返回值的作用
//011B1450 83 C4 08 add esp,8
011B1450 83 C4 08 add esp,8
//esp+8直接把定义的形参跳过去,到这一步的时候,我们就是Add的栈桢已经!!!被销毁了!!!
011B1453 89 45 E0 mov dword ptr [ebp-20h],eax
//eax里存放的是Add函数里sum的值,把eax的值给ebp-20h(ret)就把sum的值返回了

到这里函数的调用过程就结束了!!!



最终效果图如下所示:



到这里,函数的调用过程以及栈桢的创建、销毁就讲述完了!!!
















                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息