抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解(上)

简介: 抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解

导语

这篇文章是从头贯穿到尾的,让你更加详细的了解函数是什么样在内存里创建,怎么样销毁的,相信家人们读完这篇文章之后能让你眼里的代码变得透明起来(本章不需要过多了解汇编语言,重点是了解函数栈帧怎么创建和销毁的)

注意:这里我们用的是32位平台,用VS2013作为参考。

问题

大家知道这些是为什么吗?

看完这一篇,这些问题将迎刃而解。

寄存器

寄存器:

eax 通常用来执行加法,函数调用的返回值一般也放在这里面

ebx 通常用来数据存取

ecx 通常用作for循环的计数器

edx 读取I/O端口时,存放端口号

edi 字符串操作时,用于存放目的地址的,和esi两个经常搭配一起使用,执行字符串的复制等操作

今天主要的是:

ebp 栈底指针,指向栈的底部,用ebp+偏移量的形式来定位函数存放在栈中的局部变量

esp 栈顶指针,指向栈的顶部

这两个寄存器用来存放地址用来维护函数栈帧

函数栈帧

函数栈帧是什么?

C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。(来自百度百科)。

内存分布

栈区:由高地址往低地址增长,主要用来存放局部变量,函数调用开辟的空间,与堆共享一段空间。(本篇重点)

堆区:由地地址向高地址增长,动态开辟的空间就在这里(malloc,realloc,calloc,free),与栈共享一段空间。

静态区:主要存放全局变量和静态变量。

什么是栈?

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。(来自百度百科)

这里面有更详细的链接: .

详细讲解函数栈帧

栈帧的维护

这里我们用一段代码演示:

#include <stdio.h>
int Add(int x,int y)
{
  int 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函数是存入在这个地方。

当然,这个地址没有申请之前不是你的,我们需要两个寄存器维护main的这块空间。

它们就是我们之前介绍的esp和ebp:

esp和ebp中间的空间就是main函数的空间,它俩是维护函数栈帧的,调用哪个就去维护哪个函数栈帧。

这时我们在编译器里按下F10,点击调试,移动到窗口,然后点击监视,内存,反汇编。

这时我们接下来需要的三个窗口,有助于理解。

反汇编那里我们逐步分析:

首先我们要知道main函数也是被调用的(不做过多了解),调用main函数的函数一开始是被esp和ebp维护的。

开辟main函数

然后我们去看反汇编那里的指令:

push 的指令是压栈,也就是给栈放了一个元素进去,这里是把epb的值放了进去。

结果就是这个样子,我们发现,esp调到上面去了,这是因为esp的性质。

我们用调试里面的内存和监视看一看:

这是原来esp的地址:

这是第一行汇编运行后的:

我们知道地址是从高到低使用,esp向上面移动了,也就是代表地址要变小,这里减少了4。

我们再看看内存里:

确实压进去了。

第二行的指令是什么意思呢?

把esp的值给ebp(注意,esp和ebp为指针,它们里面储存的是地址)

也就是说ebp不会指向原来的位置了,和esp指向相同的位置。

变成了这个样子。

第三行指令是做什么呢?

这里只给esp减去0E4h这个值,这个值是十六进制的数字,转换十进制为228。(至于后面的h我们不做详细的讲解)

也就是说我们的esp移到了上面的某一个位置去了。

也就是说我们的esp和ebp再一次的维护了一块空间,这块空间就是我们main函数的空间。

然后下面的三行汇编指令就是压栈:

值如下:

然后是后面的指令:

第一行是把[ebp-24h]这个值给edi。

第二行把39h放在ecx里面。

第三行把0CCCCCCCCh的内容放在eax里面。

第四行是从edi开始往下的ecx空间里面放eax的值。(dword是四个字节的意思)

我们发现,edi是esp原来指向的位置,也就是这个位置:

最后面的地址我们发现是ebp的地址:

至于edi为什么地址变了,这个我们不做深究,只需要看[ebp-0E4h]的地址就可以了,因为这是没变之前edi的地址。

这里我们也发现,main函数里面都放满了0CCCCCCCCh这个值。

(这也能解释我们在打印字符数组的时候没有\0会打出来一堆乱码,因为里面都是随机值,也就是你放进去的0CCCCCCCCh。)

也就是说我们在main函数的区域里初始化了上面的蓝色值。

上面只是为main函数栈帧的开辟。

相关文章
|
6月前
|
存储 安全 C语言
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2
|
6月前
|
存储 编译器 C语言
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1
|
6月前
|
存储 编译器 程序员
C语言之反汇编查看函数栈帧的创建与销毁(一)
C语言之反汇编查看函数栈帧的创建与销毁(一)
C语言之反汇编查看函数栈帧的创建与销毁(一)
|
3月前
|
存储 C语言
【C语言】——函数栈帧的创建与销毁
【C语言】——函数栈帧的创建与销毁
|
6月前
|
存储 编译器 C语言
C语言:底层剖析——函数栈帧的创建和销毁
C语言:底层剖析——函数栈帧的创建和销毁
|
6月前
|
存储 编译器 程序员
C语言之反汇编查看函数栈帧的创建与销毁(二)
C语言之反汇编查看函数栈帧的创建与销毁(二)
|
11月前
|
编译器 C语言
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(下)
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
7天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
22 6
|
27天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10