函数的调用,栈桢的创建及销毁

*函数的调用过程(栈桢)*

下面我们主要从栈的层面深入了解c语言中函数调用的过程。

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

#include<stdio.h>
#include<stdlib.h>

int Add(int X, int Y)
{
    int z = 0;
    z = X + Y;
    return z;
}

int main()
{
    int a = 20;
    int b = 10;
    int ret = 0;
    ret = Add(a, b);
    printf("ret=%d\n", ret);
    system("pause");
    return 0;

在上面的程序里,主函数main里定义了3个局部变量,然后调用了Add函数。3个局部变量 都是在栈空间上存放的,当程序运行起来我们研究函数调用是用了哪些准备工作,准备工作做完后是如何传参的,参数传过去是如何使用参数的,参数使用完是如何销毁的,函数是如何返回的,接下来这整个过程我们通过汇编的角度来一步一步的看清楚。

下面我们进入调试功能来看程序的反汇编:
main函数汇编代码:

int main()
{
011E1810  push        ebp  
011E1811  mov         ebp,esp  
011E1813  sub         esp,0E4h  
011E1819  push        ebx  
011E181A  push        esi  
011E181B  push        edi  
011E181C  lea         edi,[ebp-0E4h]  
011E1822  mov         ecx,39h  
011E1827  mov         eax,0CCCCCCCCh  
011E182C  rep stos    dword ptr es:[edi]  
    int a = 10;
011E182E  mov         dword ptr [ebp-4],14h  
    int b = 20;
011E1835  mov         dword ptr [ebp-8],0Ah  
    int ret = 0;
011E183C  mov         dword ptr [ebp-0Ch],0  
 = Add(a, b);
011E1843  mov         eax,dword ptr [ebp-8]  
011E1846  push        eax  
011E1847  mov         ecx,dword ptr [ebp-4]  
011E184A  push        ecx  
011E184B  call        _Add (011E110Eh)  
011E1850  add         esp,8  
011E1853  mov         dword ptr [ebp-0Ch],eax  
    printf("ret=%d\n", ret);
011E1856  mov         eax,dword ptr [ebp-0Ch]  
011E1859  push        eax  
011E185A  push        offset string "ret=%d\n" (011E7B30h)  
011E185F  call        _printf (011E133Eh)  
011E1864  add         esp,8  
    system("pause");
011E1867  mov         esi,esp  
011E1869  push        offset string "pause" (011E7B3Ch)  
011E186E  call        dword ptr [__imp__system (011EB168h)]  
    system("pause");
011E1874  add         esp,4  
011E1877  cmp         esi,esp  
011E1879  call        __RTC_CheckEsp (011E112Ch)  
    return 0;
011E187E  xor         eax,eax  

}
011E1880  pop         edi  
011E1881  pop         esi  
011E1882  pop         ebx  
011E1883  add         esp,0E4h  
011E1889  cmp         ebp,esp  
011E188B  call        __RTC_CheckEsp (011E112Ch)  
011E1890  mov         esp,ebp  
011E1892  pop         ebp  
011E1893  ret  

Add函数汇编代码:

int Add(int X, int Y)
{
011E1700  push        ebp  
011E1701  mov         ebp,esp  
011E1703  sub         esp,0CCh  
011E1709  push        ebx  
011E170A  push        esi  
011E170B  push        edi  
011E170C  lea         edi,[ebp-0CCh]  
011E1712  mov         ecx,33h  
011E1717  mov         eax,0CCCCCCCCh  
011E171C  rep stos    dword ptr es:[edi]  
    int z = 0;
011E171E  mov         dword ptr [ebp-4],0  
    z = X + Y;
011E1725  mov         eax,dword ptr [ebp+8]  
011E1728  add         eax,dword ptr [ebp+0Ch]  
011E172B  mov         dword ptr [ebp-4],eax  
    return z;
011E172E  mov         eax,dword ptr [z]  
}
011E1731  pop         edi  
011E1732  pop         esi  
011E1733  pop         ebx  
011E1734  mov         esp,ebp  
011E1736  pop         ebp  
011E1737  ret  

首先,我们在这里必须的先了解俩个寄存器:

ebp
———栈底的地址
esp
———栈顶的地址
每一个函数的调用都需要在栈上开辟空间,而开辟的空间都由ebp和esp维护
而且ebp和esp维护的是调用main函数的mainCRTStartup函数的空间。
这里写图片描述
接下来我们进去main函数痕迹汇编代码一步一步来了解函数调用的过程以及栈的创建和销毁:

int main()
{
011E1810  push        ebp  

push就是压栈,这句话就是把ebp压到栈顶,esp指向栈顶。其实结果如下图所示:
这里写图片描述

011E1811  mov         ebp,esp  

这段代码就是把esp的值给ebp,也就是ebp指向esp的位置;
效果如下图:
这里写图片描述

011E1813  sub         esp,0E4h

这段代码就是esp减去0E4h,栈空间有高地址指向低地址。在这里实际就是向上开辟了0E4h大小的空间,这块空间是为main函数开辟的,esp此时指向栈顶,效果如图:
这里写图片描述

011E1819  push        ebx  
011E181A  push        esi  
011E181B  push        edi  

这三行代码,就是进行压栈操作,分别把ebx,esi,edi分别压进去,然后esp指向栈顶:
这里写图片描述

011E181C  lea         edi,[ebp-0E4h] 

这段代码:ebp-0E4h就是刚才开辟的main函数的空间。lea就是加载的意思,这一行代码就是将main函数开辟的空间加载到edi里面。

011E1822  mov         ecx,39h  
011E1827  mov         eax,0CCCCCCCCh  

将39h给ecx,在把0cccccccch给eax,

011E182C  rep stos    dword ptr es:[edi]  

这行代码的意思就是重复拷贝edi空间里面的内容,拷贝的内容,eax:
0cccccccch,拷贝次数ecx,39h次。
也就是把main函数开辟的空间地址全部初始化为0cccccccch。

这里写图片描述
……
这里写图片描述
当这4行代码全部运行完之后,查看内存可以看到从ebp-0e4h往下的57行全部被初始化为0ccccccccch。

    int a = 10;
011E182E  mov         dword ptr [ebp-4],14h  
    int b = 20;
011E1835  mov         dword ptr [ebp-8],0Ah  
    int ret = 0;
011E183C  mov         dword ptr [ebp-0Ch],0  

这三行代码就是将main函数开辟的空间初始化:a=10,地址为ebp-4;
b=20,地址为ebp-8;ret=0,地址为ebp-0Ch;效果图如下:

这里写图片描述

011E183C  mov         dword ptr [ebp-0Ch],0  
 = Add(a, b);
011E1843  mov         eax,dword ptr [ebp-8]  
011E1846  push        eax  
011E1847  mov         ecx,dword ptr [ebp-4]  
011E184A  push        ecx  
011E184B  call        _Add (011E110Eh)  
011E1850  add         esp,8  
011E1853  mov         dword ptr [ebp-0Ch],eax  

上面这段代码意思就是:将ebp-8的值(20)给eax,在将eax从栈顶压入,此时esp指向eax;然后将ebp-4的值(10)的值给ecx,再将ecx从栈顶压入,此时esp指向eax;call调用函数,调用完之后回到最近一条指令即add的位置。此时相当于栈顶又压入了add函数的地址;具体如下图所示:
这里写图片描述
然后按f11进去Add函数:

int Add(int X, int Y)
{
011E1700  push        ebp  
011E1701  mov         ebp,esp  
011E1703  sub         esp,0CCh  
011E1709  push        ebx  
011E170A  push        esi  
011E170B  push        edi  
011E170C  lea         edi,[ebp-0CCh]  
011E1712  mov         ecx,33h  
011E1717  mov         eax,0CCCCCCCCh  
011E171C  rep stos    dword ptr es:[edi]  
    int z = 0;
011E171E  mov         dword ptr [ebp-4],0  
    z = X + Y;
011E1725  mov         eax,dword ptr [ebp+8]  
011E1728  add         eax,dword ptr [ebp+0Ch]  
011E172B  mov         dword ptr [ebp-4],eax  
    return z;
011E172E  mov         eax,dword ptr [z]  
}

这里Add函数的调用同main函数调用过程相同,运行上端代码后得到结果如图示:
这里写图片描述

栈的销毁:

011E1731  pop         edi  
011E1732  pop         esi  
011E1733  pop         ebx  


pop就相当于出栈的意思,esp此时指向ebx下面的空间,这三个地址相当于被回收;

011E1734  mov         esp,ebp  
011E1736  pop         ebp  
011E1737  ret  

这里把ebp的值给esp,然后再将ebp出栈,这里的ebp是main函数的ebp,ret指令要返回值,首先把栈顶call执行下一条指令的地址出栈,然后紧接着跳到下面这一行的地址。这也就是为什么把之前的地址保存起来,起一定的返回作用。

011E1850  add         esp,8  

esp+8直接把定义的形参跳了过去,到这一步的时候,Add栈帧已经被销毁了。

011E1853  mov         dword ptr [ebp-0Ch],eax  

eax里存放的是Add函数里的的值,把eax的值放入ebp-0Ch,ret就把z的值返回了。
在栈帧销毁的过程中返回值是通过寄存器返回的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值