C语言函数栈帧的创建和销毁详解

目录
  • 写在前面
  • Add函数的调用
    • 函数传参
    • Add函数栈帧的创建
  • Add函数栈帧的销毁
  • main函数栈帧的销毁
  • 总结

写在前面

我们知道,每一次函数调用都需要在栈区上为其开辟一块空间,这块空间就叫做这个函数的栈帧。

而栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。

这样我们就了解了寄存器ebp和寄存器esp中存放的是地址,这两个地址是用来维护函数栈帧的。比如:调用main函数, 我们为main函数分配栈帧空间, 那么栈帧维护如下:

下面我们通过一段代码分析一下,函数栈帧创建和销毁的过程:(栈帧这部分内容在不同的编译器上实现存在差异, 但是思想大致都是一致的。本文是在vs2013编译器下实现的。)

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

我们在调试过程打开调用堆栈

可以看出,main函数是在__tmainCRTStartup函数内部被调用的,而__tmainCRTStartup函数又是在mainCRTStartup函数内部调用的。

为了能更加清楚的看到栈帧创建和销毁的过程,我们转到上面代码对应的反汇编代码:

int main(void)
{
009D3F40  push        ebp  //将edp压入栈帧
009D3F41  mov         ebp,esp  //将esp的值赋给edp
009D3F43  sub         esp,0E4h  //esp-0E4h
009D3F49  push        ebx
009D3F4A  push        esi
009D3F4B  push        edi
009D3F4C  lea         edi,[ebp+FFFFFF1Ch]
009D3F52  mov         ecx,39h
009D3F57  mov         eax,0CCCCCCCCh
009D3F5C  rep stos    dword ptr es:[edi]
	int a = 10;
009D3F5E  mov         dword ptr [ebp-8],0Ah
	int b = 20;
009D3F65  mov         dword ptr [ebp-14h],14h
	int ret = 0;
009D3F6C  mov         dword ptr [ebp-20h],0
	ret = Add(a, b);//计算a+b
009D3F73  mov         eax,dword ptr [ebp-14h]
009D3F76  push        eax
009D3F77  mov         ecx,dword ptr [ebp-8]
009D3F7A  push        ecx
009D3F7B  call        009D11F9
009D3F80  add         esp,8
009D3F83  mov         dword ptr [ebp-20h],eax
	printf("%d\n", ret);
009D3F86  mov         esi,esp
009D3F88  mov         eax,dword ptr [ebp-20h]
009D3F8B  push        eax
009D3F8C  push        9D5860h
009D3F91  call        dword ptr ds:[009D9118h]
009D3F97  add         esp,8
009D3F9A  cmp         esi,esp
009D3F9C  call        009D1140
	return 0;
009D3FA1  xor         eax,eax
}
009D3FA3  pop         edi
009D3FA4  pop         esi
009D3FA5  pop         ebx
009D3FA6  add         esp,0E4h
009D3FAC  cmp         ebp,esp
009D3FAE  call        009D1140
009D3FB3  mov         esp,ebp
009D3FB5  pop         ebp
009D3FB6  ret

main函数的调用 main函数栈帧的创建

经过刚才我们的理解,在准备调用main函数的时候,调用main函数的那个函数的栈帧已经开辟好了。

然后将ebp压入栈帧,保存了指向栈底的ebp的地址,而此时esp指向新的栈顶位置;接着将esp的值赋给了ebp,产生了新的ebp;用esp减去一个16进制数0E4H(这里就是为main函数预开辟空间)。紧接着三个压栈指令,分别将ebx,esi,edi,压入栈帧。加载完有效地址以后,将为main函数预开辟空间全部初始化为0xCCCCCCCC。最后创建了三个局部变量a,b,ret并进行了初始化。

Add函数的调用

函数传参

将b的值存入寄存器eax中,再将eax压入栈中;将a的值存入寄存器ecx中,再将将ecx压入栈中;这里看出参数是从右向左传递的。紧接着执行call指令,这里就是调用Add函数,同时将call指令的下一条指令的地址压入栈中,然后执行call指令的时候按F11 , 就进入了Add函数内部。

Add函数栈帧的创建

首先将main()函数的ebp压入栈,保存指向main()函数栈帧底部的ebp的地址,此时esp指向新的栈顶位置;将esp的值赋给ebp,产生新的ebp,即Add()函数栈帧的ebp;给esp减去一个16进制数0E4H,这里是为Add()函数预开辟空间;紧接着三个压栈指令,分别将ebx,esi,edi,压入栈帧。加载完有效地址以后,将为Add函数预开辟空间全部初始化0xCCCCCCCC。在紧接着创建了变量z,将形参的a和b相加的结果存储到z中;最后将结果存储到eax寄存器中,通过寄存器带回了函数的返回值。

Add函数栈帧的销毁

edi、esi、ebx依次出栈,esp 会向下移动;然后将ebp的值赋给esp,使esp指向ebp指向的地方;接着ebp 出栈,同时将出栈的内容给ebp,此时ebp又指向了main函数栈帧的底部,最后执行ret 指令,表示出栈一次,并跳转到出栈的内容的地址处,也就是call指令的下一条指令处。

main函数栈帧的销毁

main函数栈帧的销毁和Add函数栈帧销毁的过程的思想都是一样的,这里就不做多赘述了。

总结

通过上面的例子,我们知道了局部变量是如何创建的,知道了为什么创建局部变量不初始化,会导致里面的内容是随机值;对函数是如何传参的,以及传参顺序是如何也有了较为深入的了解。

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • C语言函数栈帧详解

    目录 前言 一.函数栈帧是什么? 二.栈帧准备知识 1.内存分区 2.什么是栈? 三.详解栈帧创建与销毁全过程 调用函数之前: 将传入函数的值放入栈中 函数执行: 1.保护当前ebp 2.创建所需调用函数的栈帧空间 3.保存局部变量 4.参数运算 函数返回: 1.存储返回值 2.销毁空间 3.ebp回上一栈帧栈底 4.销毁形参 5.main函数拿到返回值 总结 前言 在c语言中我们会将一些功能单独写成一个函数,以供主函数调用,在表面来看调用的过程就是写出一个函数后,只需要在调用时中通过函数名将实

  • 详细理解函C语言的函数栈帧

    目录 一.函数栈帧的创建 1.寄存器 2.函数栈帧 3.函数中调用函数 二.函数栈帧的销毁 总结 一.函数栈帧的创建 1.寄存器 一般来说,计算机中的寄存器有六种 分别是:eax, ebx, ecx,edx,ebp,esp 而ebp,esp这两个寄存器中存放的是地址,与此同时,这两个地址是来维护函数栈帧的. 2.函数栈帧 每一个函数的调用,都需要在栈区为其开辟一个空间,这块为其开辟的空间就是函数栈帧. ebp:栈底指针 esp:栈顶指针 对于栈这种数据结构一共有两种栈操作 1.pop 出栈 2.

  • C语言深入讲解之从函数栈帧角度理解return关键字

    目录 初识函数栈帧 return 个人总结环节 初识函数栈帧 如上图可见,函数在被调用的时候会现在栈上开辟一个空间,我们称之为栈帧,之后函数内部的变量在这块区域进行空间开辟. 但是函数在调用的时候,怎么知道需要开辟多大空间呢??? void func() { int a, b; double c, d, e; } 按照示例代码,会先对需要的内存空间大小进行预估,然后进行空间开辟. 函数返回时,栈帧会被释放,但是,虽然栈帧被释放,里面的内容是不会被清空的,下面通过以下的例子进行分析. #inclu

  • c语言函数栈帧的创建和销毁过程详解

    目录 1 相关知识介绍 1.1 寄存器 1.2 函数栈帧概述 2 栈帧创建与销毁过程 1 相关知识介绍 1.1 寄存器 一般计算机内通用寄存器包括eax,ebx,ecx,edx,esi,edi,esp,edp,其中esp,ebp这两个寄存器是用来存放地址的,这两个地址就是用来维护函数栈帧的 1.2 函数栈帧概述 我们知道c语言中函数都是被调用的,main函数里面能调用其他函数,其实main函数也是被别的函数调用的.main函数是在 _tmainCRTSartup 函数中被调用的,_tmainCR

  • C语言函数栈帧的创建和销毁介绍

    在初学c语言中,很多时候要记的内容有点多,有时候并不能深入的了解它.关于函数的栈帧可以帮助我们深入了解函数传参的过程,让我们了解c语言. 以下是我们平时接触过,但不了解的问题: 1.为什么局部变量在未赋值前是随机的. 2.局部变量创建的过程. 3.函数传参,传参的顺序问题. 4.形参与实参的关系什么. 5.调用函数是怎么调用的,调用的过程是什么. 6.调用函数结束后,是怎样返回的. 这些问题我们在学校可能并不会接触,也不会出现在考试的试卷上,但是作为计算机专业的学生,做一些认识和了解是很有必要的

  • C语言函数栈帧解析

    目录 一.什么是函数栈帧 1.寄存器: 2.函数栈帧 3.栈帧的作用和维护 4.栈帧结构 二.函数栈帧的创建 1.汇编代码 2.main函数 2.栈帧创建: 3.步骤 4.ADD函数栈帧的创建 三.函数栈帧的销毁 1.汇编语言 四.了解 1.函数传参 2.函数返回值如何返回 3.函数中变量如何初始化和赋值 总结 一.什么是函数栈帧 1.寄存器: eax, ebx, ecx ... ebp - 存放了指向函数栈帧栈底的地址 esp - 存放了指向函数栈帧栈顶的地址 2.函数栈帧 函数被调用时,系统

  • C语言函数栈帧的创建与销毁详解

    目录 前言 一.函数栈帧是什么? 1.寄存器 2.ebp与esp 二.函数栈帧的创建 1.代码块 2.调用堆栈 3.esp与ebp如何维护栈帧 总结 前言 大家在学习的时候一定有以下困惑: 局部变量是怎么创建的?为什么局部变量的值是随机值?函数是怎么传参?传参的顺序是怎样的?形参与实参是什么关系?函数调用是怎么做到的?函数调用完成不是销毁了吗,如何带回的返回值? 以上这些都可以通过了解函数栈帧的创建与销毁来理解.接下来我就带大家来了解函数栈帧的创建与销毁. 本次使用的编辑器是VS2013,因为越

  • C语言函数栈帧的创建和销毁详解

    目录 写在前面 Add函数的调用 函数传参 Add函数栈帧的创建 Add函数栈帧的销毁 main函数栈帧的销毁 总结 写在前面 我们知道,每一次函数调用都需要在栈区上为其开辟一块空间,这块空间就叫做这个函数的栈帧. 而栈是从高地址向低地址延伸的.每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息.寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址). 这样我们就了解了寄存器ebp和寄存器esp中存放的是地址,这两个地址是用来维护函数栈

  • C语言函数栈帧的创建与销毁原理图解

    目录 什么是函数栈帧 什么是栈? 与函数栈帧有关的汇编语句 函数如何创建栈帧并销毁 main函数栈帧开辟 调用Add函数 返回主函数 什么是函数栈帧 我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的. 那函数是如何调用的?函数的返回值又是如何待会的?函数参数是如何传递的?这些问题都和函数栈帧有关系. 函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放: 函数参数和函数返回值 临时变量

  • C语言中函数栈帧的创建和销毁的深层分析

    目录 一.本文目标 二.基础知识 1.寄存器 2.代码案例 3.总体栈帧概况 4.所需反汇编代码总览 三.函数栈帧创建销毁过程 1._tmainCRTStartup函数(调用main函数)栈帧的创建 2.main函数栈帧的创建 3.main函数内执行有效代码(变量) 4.Add函数栈帧的创建 5.Add函数内执行有效代码 6.Add函数栈帧的销毁 7.main函数栈帧的销毁 四.总结 一.本文目标 1.局部变量是怎么创建的? 2.为什么局部变量的值是随机值? 3.函数是怎么传参的?传参的顺序是怎

  • C语言超详细讲解函数栈帧的创建和销毁

    目录 1.本节目标 2.相关寄存器 3.相关汇编指令 4.什么是函数栈帧 5.什么是调用堆栈 6.函数栈帧的创建和销毁 (1).main函数栈帧的创建与初始化 (2).main函数的核心代码 (3).Add函数的调用过程 (4).Add函数栈帧的销毁 (5).调用完成 7.对开篇问题的解答 1.本节目标 C语言绝命七连问,你能回答出几个? 局部变量是如何创建的?为什么局部变量不初始化其内容是随机的?有些时候屏幕上输出的"烫烫烫"是怎么来的?函数调用时参数时如何传递的?传参的顺序是怎样的

  • C语言详尽图解函数栈帧的创建和销毁实现

    目录 常见寄存器 基本的汇编语言知识 具体实现 关于栈帧创建与销毁的问答题 注:本文章所使用的编译器是VS2010,由于不同编译器的函数栈帧与销毁略有差异,所以具体细节请读者自行实践! 常见寄存器 寄存器有:eax.ebx.ecx.edx.edi.esi.ebp.esp 其中 ebp 和 esp 是用来维护函数栈帧的,他们里面存放的是地址. 他们维护的是某个正在被调用的函数.通常我们又称 ebp 为栈底指针,称 esp 为栈顶指针 基本的汇编语言知识 push:压栈 pop:出栈 mov:若有变

随机推荐