堆栈和函数调用过程

一、堆和栈
(1)首先要清楚的是程序对内存的使用分为以下几个区:
1、 栈区(stack):由编译器自动分配和释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。栈用于维护函数调用的上下文,离开了栈函数调用就没法实现。
2、 堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。与数据结构中的堆是两码事,分配方式类似于链表。堆是用来容纳应用程序动态分配的内存区域,当程序使用malloc或new分配内存时,得到的内存来自堆里。
3、 全局区(static):全局变量和静态变量存放在此。
4、 常量区:常量字符串放在此,程序结束后由系统释放。
5、 代码区:存放函数体的二进制代码。

Linux下一个进程里典型的内存布局
这里写图片描述
(2)申请方式
栈由系统自动分配,速度较快,在windows下栈是向低地址扩展的数据结构,是一块连续的内存区域,大小是2MB。
堆需要程序员自己申请,并指明大小,速度比较慢。在C中用malloc,C++中用new。堆是向高地址扩展的数据结构,是不连续的内存区域,堆的大小受限于计算机的虚拟内存。因此堆空间获取和使用比较灵活,可用空间较大。

二、栈帧
1、栈在函数调用中的作用:参数传递、局部变量分配、保存调用的返回地址、保存寄存器以供恢复。
2、栈帧(stack Frame):一次函数调用包括将数据和控制从代码的一个部分传递到另外一个部分,栈帧与某个过程调用一一映射。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低址地)。
3、函数调用规则:
_cdecl:按从右至左的顺序压参数入栈,由调用者把参数弹出栈。由于每次函数调用都要由编译器产生清楚堆栈的代码,所以使用_cdecl的代码比使用_stdcall的代码要大很多,但是这种方式支持可变参数。对于C函数,名字修饰约定为在函数名前加下划线。对于C++,除非特变使用extern C,C++使用不同的名字修饰方式。
_stdcall:按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数。
_fastcall:主要特点就是快,因为它是通过寄存器来传送参数的,和__stdcall很象,唯一差别就是头两个参数通过寄存器传送。注意通过寄存器传送的两个参数是从左向右的,即第一个参数进ecx,第2个进edx,其他参数是从右向左的入stack。返回仍然通过eax。
三、函数调用过程

int sum(int a,int b)
{
    int temp = 0;
    temp = a+b;
    return temp;
}
int main()
{
    int a = 10;
    int b = 20;
    int ret = 0;
    ret = sum(a,b);
    cout<<ret<<endl;

    return 0;
}

//转到反汇编
int sum(int a,int b)
{
00D6AA00  push        ebp  
00D6AA01  mov         ebp,esp  
00D6AA03  sub         esp,0CCh  
00D6AA09  push        ebx  
00D6AA0A  push        esi  
00D6AA0B  push        edi  
00D6AA0C  lea         edi,[ebp-0CCh]  
00D6AA12  mov         ecx,33h  
00D6AA17  mov         eax,0CCCCCCCCh  
00D6AA1C  rep stos    dword ptr es:[edi]  
    int temp = 0;
00D6AA1E  mov         dword ptr [temp],0  
    temp = a+b;
00D6AA25  mov         eax,dword ptr [a]  
00D6AA28  add         eax,dword ptr [b]  
00D6AA2B  mov         dword ptr [temp],eax  
    return temp;
00D6AA2E  mov         eax,dword ptr [temp]  
}
00D6AA31  pop         edi  
00D6AA32  pop         esi  
00D6AA33  pop         ebx  
00D6AA34  mov         esp,ebp  
00D6AA36  pop         ebp  
00D6AA37  ret

int main()
{
00FBAB60  push        ebp  
00FBAB61  mov         ebp,esp  
00FBAB63  sub         esp,0E4h  
00FBAB69  push        ebx  
00FBAB6A  push        esi  
00FBAB6B  push        edi  
00FBAB6C  lea         edi,[ebp-0E4h]  
00FBAB72  mov         ecx,39h  
00FBAB77  mov         eax,0CCCCCCCCh  
00FBAB7C  rep stos    dword ptr es:[edi]  
    int a = 10;
00FBAB7E  mov         dword ptr [a],0Ah  
    int b = 20;
00FBAB85  mov         dword ptr [b],14h  
    int ret = 0;
00FBAB8C  mov         dword ptr [ret],0  
    ret = sum(a,b);
00FBAB93  mov         eax,dword ptr [b]  
00FBAB96  push        eax  
00FBAB97  mov         ecx,dword ptr [a]  
00FBAB9A  push        ecx  
00FBAB9B  call        sum (0FB1195h)  
00FBABA0  add         esp,8  
00FBABA3  mov         dword ptr [ret],eax  
    cout<<ret<<endl;
00FBABA6  mov         esi,esp  
00FBABA8  mov         eax,dword ptr ds:[00FC7364h]  
00FBABAD  push        eax  
00FBABAE  mov         edi,esp  
00FBABB0  mov         ecx,dword ptr [ret]  
00FBABB3  push        ecx  
00FBABB4  mov         ecx,dword ptr ds:[0FC7378h]  
00FBABBA  call        dword ptr ds:[0FC735Ch]  
00FBABC0  cmp         edi,esp  
00FBABC2  call        __RTC_CheckEsp (0FB1550h)  
00FBABC7  mov         ecx,eax  
00FBABC9  call        dword ptr ds:[0FC7358h]  
00FBABCF  cmp         esi,esp  
00FBABD1  call        __RTC_CheckEsp (0FB1550h)  

    return 0;
00FBABD6  xor         eax,eax  
}

首先可以发现在高级源代码之前都有固定的汇编指令
00FBAB60 push ebp
00FBAB61 mov ebp,esp
00FBAB63 sub esp,0E4h
00FBAB69 push ebx
00FBAB6A push esi
00FBAB6B push edi
00FBAB6C lea edi,[ebp-0E4h]
00FBAB72 mov ecx,39h
00FBAB77 mov eax,0CCCCCCCCh
00FBAB7C rep stos dword ptr es:[edi]
每个函数在调用前都会做一件事情。
(1)把调用方的ebp入到自己的栈里去
(2)用esp指向新的栈顶位置,把esp的值赋给ebp,产生新的ebp
(3)开辟空间,初始0XCCCCCCCC。
ebp入栈,实参入栈,形参入栈,调用call指令。call有两步,把下一行指令的地址入栈,跳转到调用的函数执行。形参内存主调方开辟,主调方释放。

在栈上访问局部变量都是通过ebp指针的偏移量,局部变量属于指令。
C和C++调用函数时实参从右向左压。因为C和C++支持可变参数函数

main函数栈布局
这里写图片描述

sum函数栈帧的开辟
这里写图片描述

sum函数的内存布局
这里写图片描述

sum函数栈帧的回退
00D6AA34 mov esp,ebp //把ebp的值给esp
这里写图片描述
00D6AA36 pop ebp //出栈。esp向下挪,把出栈的元素赋给ebp
这里写图片描述
00D6AA37 ret
ret包含两个动作:
1、出栈 ,栈顶指针向下移。出栈元素是下一行指令地址,赋给PC寄存器,PC寄存器永远存放下一行指令的地址。
2、ret运行完以跳转到下一行指令的地址
00FBABA0 add esp,8 //回退形参变量
这里写图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值