【C语言:函数栈帧的创建与销毁】

文章目录

前言

学习过C语言后,你是否有一下疑问

  1. 局部变量在内存中是如何创建?
  2. 变量不初始化为什么是随机值?
  3. 函数是如何传参的?顺序是什么?
  4. 形参和实参是什么关系?
  5. 函数是如何调用以及返回的?
  6. 烫烫烫烫烫是怎么打印出来的呢?

带着这些疑问,我们来学习下面的知识:

一、前期准备

1.寄存器

名称 介绍
eax "累加器" 它是很多加法乘法指令的缺省寄存器。
ebx 基地址"寄存器, 在内存寻址时存放基地址
ecx 计数器,是重复(REP)前缀指令和LOOP指令的内定计数器。
edx 总是被用来放整数除法产生的余数。
esi 源索引寄存器
edi 目标索引寄存器
ebp "基址指针",存放的是地址,用来维护函数栈帧(栈底指针)
esp 专门用作堆栈指针,存放的是地址,用来维护函数栈帧(栈顶指针

相信学过微机原理的同学都应该了解这些,我们今天会重点使用这两个寄存器。

2.汇编指令

接下来还有一些汇编代码的含义:

  • lea:Load effective address的缩写,取有效地址
  • call:用于调用其他函数
  • mov:数据传送指令,用于将一个数据从源地址传送到目标地址
  • sub:减法,
  • add:加法
  • pop:出栈
  • push:入栈或压栈

3.测试代码

c 复制代码
#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 c = 0;
	c = add(a,b);
	printf("%d\n", c);
	return 0;
}

二、解开函数栈帧的神秘面纱

1.栈帧大体轮廓

学习过C语言的函数我们都知道,每一次的函数调用就会在内存中(栈区)创建一个空间。

main函数也是被调用的,但是谁来调用main函数呢?在VS2013中,通过调试我们可以发现

main函数被 __tmainCRTStartup() 调用

而 __tmainCRTStartup() 又被 mainCRTStartup() 调用,栈区一般是从高地址向低地址使用的所以我们可以画出下图:

2.main函数栈帧的创建

我们F10调试起来,然后转到反汇编就可以观察main函数是怎么执行的

首先,我们应该明白,在进入main函数之前。我们内存中应该是这样布局的:

然后我们进入main函数,执行汇编代码

  • push ebp 进行压栈,ebp 在 __tmainCRTStartup() 上面压栈,我们通过监视和内存可以看到
    esp地址减少了4个字节,并且内存中也有了ebp


  • 接下来是 mov ebp,esp ,将esp的值传入ebp中(即将ebp指针移动到esp指向的位置),可以看到ebp的地址发生了变化
  • 在继续执行 sub esp,0E4h,将esp的内容减去0E4h(将esp移动到原esp-0E4h的位置,esp-0E4h地址减小),esp的地址变小了,说明往上走了。
  • 此时esp与ebp指向了另一块空间,正是为main函数开辟了栈帧


  • 此时,又执行了三句push 代码,esp的地址依旧减小




  • 接下来,执行 lea edi,[ebp-0E4h],把 ebp - 0E4h 这个地址加载到 edi 里(建议使用vs2013,其它编译器版本太高,可能不会这样,)
  • 这个0E4h是不是有点熟悉,我们的esp是不是也减过0E4h,所以我们的edi中放的就是esp在三次push之前的位置

接下来,执行一下代码

c 复制代码
mov  ecx,9,
mov  eax,0CCCCCCCCh 
rep stos  dword ptr es:[edi]

这三句代码是什么意思呢?

就是从edi位置开始,ecx这么多的空间(9行的空间),全部初始化为0CCCCCCCC


到这为止,main函数的栈帧就创建好了

3.main函数内执行有效代码

接下来,机器才开始执行我们在main函数中书写的代码

  • mov dword ptr [ebp-8],0Ah ,把 0Ah(十进制为10) 放到 ebp-8 的位置
  • mov dword ptr [ebp-14h],14h ,把 14h(20) 放到 ebp-14h的位置
  • mov dword ptr [ebp-20h],0 ,把 0 放到 ebp-20h的位置

执行前:

执行后:

也就是

4.烫烫烫

此时,如果我们的变量未初始化,它里面存放的就是CCCCCCCC,那么你把他打印出来,是不是就是我们的随机值(烫烫烫烫烫)呢?很显然就是这个原因

5.函数参数的传递

add函数又是怎么创建的呢?我们继续执行代码

  • eax,dword ptr [ebp-14],这句代码就是将我们的20放进eax中
  • push eax, ,然后push eax
  • eax,dword ptr [ebp-8],这句代码就是将我们的20放进ecx中
  • 然后push ecx

这几句代码好像是在传递参数,可我们的add函数的栈帧还没有创建,那是在传参吗?----确实是在传参


6.add函数栈帧的创建

按 F11,进入到 Add 函数 ,该add 函数地址不一定与main 函数地址相连,但是add 函数的地址一定在main 函数地址上面

执行call指令后,我们发现它里面放的是一个地址------006118f7

仔细观察我们发现,这个地址就是call指令下一条指令的地址。那它记这个地址干什么呢?-----add函数调用完,回到call指令的下一条指令位置继续执行下面的代码

接下来,机器继续执行以下代码,和main函数栈帧创建时一样,这里就不在赘述了



7.add函数内执行有效代码

此时,c被设置为0


8.add是如何获得参数的

c 复制代码
0061187C  mov         eax,dword ptr [ebp+8] 
//将ebp+8位置的值放进eax  ,eax=10
0061187F  add         eax,dword ptr [ebp+0Ch]  
//eax再加上ebp+12位置的值 	,eax =eax + 20 = 30
00611882  mov         dword ptr [ebp-8],eax  
//再将eax放到ebp-8位置

这里我们发现,函数的参数是在栈中找到的我们之前压进栈中的值,其实我的add函数压根就没有去找a,b。这更加证实了形参是实参的一份临时拷贝

此时,已经算出了结果,我们是怎么返回的呢?

c 复制代码
00611885  mov         eax,dword ptr [ebp-8] 

将ebp-8位置的值放进eax寄存器中,add函数结束z的值就销毁了,但是寄存器不会销毁,刚好可以带回我们的值。

9. add函数栈帧的销毁

执行pop弹出栈,esp地址增大




执行这两句代码。esp移动到ebp位置。ebp移动到以前的ebp位置,esp再pop一次

执行最后一条ret指令,此时该位置刚好是call指令的下一条指令的地址。

10.main函数栈帧的销毁

再执行esp+8,即向高位移动,实际上这条指令就是在销毁我们的形参

此时,再执行 006118FA mov dword ptr [ebp-20h],eax ----将eax中的值放在ebp-20(c)中,而eax中刚好又放的是我们add函数执行的结果

接下来就是打印值

销毁eax中的值

main函数函数栈帧销毁,都与上面类似,这里不多做赘述

三、总结

  1. 局部变量在内存中是如何创建?

首先为这个函数分配好栈帧空间,并初始化一部分空间为CCCCCCCC,再为局部变量分配空间并初始化

  1. 变量不初始化为什么是随机值?

因为是在栈帧创建时的随机初始化为CCCCCCCC

  1. 函数是如何传参的?顺序是什么?

在调用函数前,形参已经被压入到栈中。进入函数后,通过指针偏移找到参数

  1. 形参和实参是什么关系?

形参是实参的一份临时拷贝

  1. 函数是如何调用以及返回的?

函数会在调用前就记住,调用位置下一条指令的地址,调用结束后,直接回到调用位置下一条指令

  1. 烫烫烫烫烫是怎么打印出来的呢?

还是因为栈帧创建时的随机初始化为CCCCCCCC

如有错误,请大佬指正!

相关推荐
凹凸曼说我是怪兽y1 分钟前
python后端之DRF框架(上篇)
开发语言·后端·python
l1t10 分钟前
修改DeepSeek翻译得不对的V语言字符串文本排序程序
c语言·开发语言·python·v语言
KarrySmile15 分钟前
Day17--二叉树--654. 最大二叉树,617. 合并二叉树,700. 二叉搜索树中的搜索,98. 验证二叉搜索树
数据结构·算法·二叉树·二叉搜索树·合并二叉树·最大二叉树·验证二叉搜索树
凤年徐16 分钟前
【数据结构与算法】21.合并两个有序链表(LeetCode)
c语言·数据结构·c++·笔记·算法·链表
z樾19 分钟前
Sum-rate计算
开发语言·python·深度学习
程序员老冯头25 分钟前
第三十二节 MATLAB函数
数据结构·算法·matlab
lifallen29 分钟前
hadoop.yarn 带时间的LRU 延迟删除
java·大数据·数据结构·hadoop·分布式·算法
钮钴禄·爱因斯晨34 分钟前
赛博算命之八字测算事业运势的Java实现(四柱、五行、十神、流年、格局详细测算)
java·开发语言·aigc
_extraordinary_1 小时前
Java Map和Set
java·开发语言
jingjing~1 小时前
【Qt】QTime::toString(“hh:mm:ss.zzz“) 显示乱码的原因与解决方案
java·开发语言·qt