函数栈帧的创建与销毁

学C语言时,局部变量、函数运用广泛

但他们的底层原理如何,听我细细道来。

目录

一、预备知识

1.汇编语言简介

2.寄存器

3.栈(stack)

二、函数栈帧

过程详析:

 1.main函数的汇编过程

 2.局部变量的汇编过程

 3.调用函数的汇编过程

 4.结尾回收空间 

​ 5.回到main函数

三、结尾


一、预备知识

1.汇编语言简介

在这之前,需要先了解一下汇编语言的知识。

凡是一门高级语言,机器不可能直接识别,会通过编译器转换为低级语言。

汇编语言正是这样一门低级语言。

可它一点也不低级,所有编译语言最终都要经过它之手才可被机器识别。

2.寄存器

汇编语言中,有个很重要的概念叫作寄存器。

寄存器,说白了就是用来存储数据、地址的,它的优先级最高,CPU优先使用其内部数据。

寄存器依靠名称来区分数据,而不通过地址,可以把他们都当作变量看待。

寄存器的名字最常用的有以下几种:

 看不懂没关系,后面会用例子来解释。

3.栈(stack)

栈,就是内存的一种模型。

可以把它理解成一个桶,用来装一些临时占用内存的数据,比如函数、局部变量。

另外,栈的使用是从高地址到低地址调用的。

 每当有一个函数开始执行,都会在栈里建立一个 (frame), 所有变量放入帧中。

当函数执行结束,帧便被自动收回,释放内存空间。

二、函数栈帧

有了上面知识,我们步入正题。首先邀请两位主角,ebpesp

他们都是寄存器,用来存放地址,这个地址是用来维护函数栈帧的。

接下来看一串示例代码:

int add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = add(a, b);
	printf("%d\n", c);
	return 0;
}

 这串代码以 main() 函数为起点,main也是函数,需要在内存开辟一块空间。

ebp和esp 就分别去维护那一块的栈帧

但是这里问题来了,main函数又是被谁调用的呢?

在VS2013中,main函数是由__tmainCRTStartup 函数调用的,

而__tmainCRTStartup 又是被mainCRTStarup 所调用。

 因为都是函数也要有栈帧。

过程详析:

1.main函数的汇编过程

 一开始内存是这样的,要对内存直接动手,还是要请到我们的汇编代码。

 这便是main函数调用的一系列汇编代码。不怕,我会一步一步地拆分解析。

看第一行,指的是将 ebp 这个寄存器放到__tmainCRTStartup 上,也叫压栈。

 将ebp 压上去后,esp也往上指,即esp向上移动4个字节的空间。

再看这条指令

 指的是将esp 的值给ebp,也就是ebp 也往上指了,它们指向了同一空间。

 

 再看这条指令,即将 esp 地址减去 0E4h,对应图就是将 esp 往上指。

(减了就是变小,途中对应的下面是高地址,上面是低地址,所以往上)

结果可以发现,ebp和esp都往上了,他们来到了一块新大陆。

这块大陆就是为main函数开辟的空间

 

这三条连一起,刚刚讲过push,即压栈,

最后把 esp 向上,即地址减去12个字节。

如图:

 

这四步一起看,lea 指load effective address(加载有效地址), 即将后面这个地址放到edi里去。

ebp-24h 是将ebp减去一段地址,到达ebx的位置。

 最后一步要配合前面三步,所以他们的整体意思是:

从edi开始到ebp,中间的9个空间,全部转换为0CCCCCCCCh。

 2. 局部变量的汇编过程

 走了半天,main函数终于结束了。我们来看以下的局部变量汇编代码。

 mov,  即将 0Ah 放入ebp-8的地址中,0Ah就是我们 a的值10,如图:

(这里 ebp 往上都是main函数的预留空间)

 以此类推,后面赋值的代码也就好解释了。

(这里的14h和20h对应内存中如图,他们中间都隔着2串cc)

赋值完成,结果如图:

 3.调用函数的汇编过程

局部变量定义完后,终于到了我们的自定义函数阶段,函数内部图如下:

从头来看

 ebp-14h 这块地址放着20,mov语句将值赋给 eax 上,再将eax压入栈。

 这一步同理,ebp - 8的值为10,mov语句将值赋给ecx, 再将 ecx 压入栈。

(其实这一步就是在创建局部变量x,y)

再看这条,call的作用是先调用函数,函数结束后会来到下一条指令的地址,先将其压栈

 

 

 此刻便进入函数

  进入函数,会发现一些与main函数相似的地方,其实就是再内存里给函数预留空间的作用。

过程和main函数一样,这里不细说了,直接看结果:

 空间预留好了,看函数内部。

第一步,将ebp-8的地址处赋值为0(即创建一个局部变量z);

第二步,将ebp+8的位置的值赋值给eax,

(ebp+8的结果在下面,即我们刚刚创建的局部变量x)

再将eax加上ebp+0Ch地址的值,这个加后的结果保存在eax中。

(ebp+0Ch即局部变量y)

 再将eax内的 30 给ebp-8 的位置上(此时ebp-8位置上的变量就是z)

 函数内最后一句话

将ebp-8 的值30给eax。因为eax是一个寄存器,不会销毁,而局部变量z会出函数而销毁。

4.结尾回收空间 

先看最后的汇编代码

 pop与push相反,它指将其从栈内弹出,可以理解为从桶里把他们按顺序拿出来。

每次拿出来 esp 都要顺势往下,如图:

 其实,此时的add函数栈帧也失去了意义,会被一起回收。

这是最后三条指令。第一条将ebp赋给esp,即esp往下到达和ebp同样的位置。

再将此时ebp弹出,回到一开始的地方,最后整个程序又回到了main函数内。

 5.回到main函数

esp到达了我们刚刚call留下的地址 ,函数退出后便会来到这里,如下图。

 来到这串地址,还剩下两串汇编代码。

第一步将esp+8,就是esp往下,此时形参便被销毁了。如图:

最后将eax寄存器内刚刚存下的30给ebp-20h地址的局部变量c

 这个函数结束了。

 最后把main函数剩余空间清理掉即可,与add函数同理。

三、结尾

 本篇博客尽可能通俗地讲述函数栈帧的实现,如能帮到大家欢迎大家给一个赞。

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丶chuchu丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值