函数的栈帧

什么是栈帧?
栈帧也叫过程活动记录,是编译器用来实现函数调用过程的一种数据结构。C语言中,每个栈帧对应着一个未运行完的函数。从逻辑上讲,栈帧就是一个函数执行的环境:函数调用框架、函数参数、函数的局部变量、函数执行完后返回到哪里等等。栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。
我学习了函数之后,写了这篇博客,希望能帮助到别人,如果有漏洞或错误可以给我留言,请指教!!!
我以Add()函数为例来分析一下:
(注意:我用的编程工具是vs2013)
先看一段简单的代码:

#include "stdio.h"
int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}
int main()
{
    int a = 10;
    int b = 20;
    int ret = Add(a, b);
    printf("ret=%d\n",ret);
        return 0;
}

当讲程序调试的时候, 查看【调用堆栈】(按F10进入调试-窗口-调用堆栈,或按快捷键ctrl+alt+C) ,用VS2013调试 如下图:
这里写图片描述

我们发现其实main函数在 __tmai nCRTStartup 函数中调用的,而 __tmai nCRTStartup 函数是在 mai nCRTStartup 被调用的。我们知道每一次函数调用都是一个过程。这个过程我们通常称之为: 函数的调用过程。这个过程要为函数开辟栈空间, 用于本次函数的调用中临时变量的保存、 现场保护。 这块栈空间我们称之为函数栈帧。
而栈帧的维护我们必须了解ebp和esp两个寄存器。 在函数调用的过程中这两个寄存器存放了维护这个栈的栈底和栈顶指针。比如:调用main函数, 我们为main函数分配栈帧空间, 那么栈帧维护如下:
这里写图片描述

ebp存放了指向函数栈帧栈底的地址。esp存放了指向函数栈帧栈顶的地址。
注意:ebp指向当前位于系统栈最上边一个栈帧的底部,而不是系统栈的底部。严格说来,“栈帧底部”和“栈底”是不同的概念;ESP所指的栈帧顶部和系统栈的顶部是同一个位置。
1 . 从main函数的地方开始, 要展开main函数的调用就得为main函数创建栈帧, 那我们先来看main函数栈帧的创建。转到反汇编可以更清晰的看到过程:
这里写图片描述
过程分析:

a.首先mainCRTStartup(),__mainCRTStartup()函数的调用,调main()函数;

b.将ebp压栈处理,保存指向栈底的ebp的地址(方便函数返回之后的现场恢复),此时esp指向新的栈顶位置;

c.将esp的值赋给ebp,产生新的ebp;

d.给esp减去一个16进制数0E4H(为main函数预开辟空间);

e.push ebx、esi、edi;

f.lea指令,加载有效地址;

g.初始化预开辟的空间为0xcccccccc;

h.创建变量a与b。
1. 接下来是Add函数的调用。

参数传递过程:
这里写图片描述
执行call指令的时候按F11 , 来到了这里。
这里写图片描述
再按F11 就进入Add函数的执行代码处。
Add函数栈帧的创建:
这里写图片描述
剩下的就是是函数返回部分:
这里写图片描述
这里写图片描述
注: 栈帧这部分内容在不同的编译器上实现存在差异, 但是思想都是一致的
我们平时说的堆栈其实是指栈,而实际上堆和栈是两种不同的内存分配。简单罗列如下各方面的异同点。
1).堆需要用户在程序中显式申请,栈不用,由系统自动完成。申请/释放堆内存的API,在C中是malloc/free,在C++中是new/delete。申请与释放一定要配对使用,否则会造成内存泄漏(memory leak),久而久之系统就无内存可用了,出现OOM(Out Of Memory)错误。一般在return/exit或break/continue等语句时容易忘记释放内存,所以检查内存泄漏的代码时要关注这些语句,看它们前面是否有必要的释放语句free/delete。
2).堆的空间比较大,栈比较小。所以申请大的内存一般在堆中申请;栈上不要有较大的内存使用,比如大的静态数组;而且除非算法必要,否则一般不要使用较深的迭代函数调用,那样栈消耗内存会随着迭代次数的增加飞涨。
3).关于生命周期。栈较短,随着函数退出或返回,本函数的栈就完成了使用;堆就要看什么时候释放,生命周期就什么时候结束。

为什么研究栈帧?看一个题目 :

#include <stdi o. h>
void fun()
{
int tmp = 10;
int *p = (int *) (*(&tmp+1) ) ;
*(p-1) = 20;
}
int main()
{
int a =0;
fun() ;
printf("a = %d\n", a) ;
return 0;
}

事实上在不同平台下这段代码有不同的输出,我就不多说了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值