目录
一、前言
二、基础知识介绍
2.1 寄存器介绍
2.2、汇编指令介绍
三、函数栈帧的创建销毁过程
3.1 调用main函数的函数
3.2 main函数开辟栈帧
3.3 在main函数中创建变量
3.4 调用Add函数前的准备
3.5 为Add函数开辟栈帧
3.6 在Add函数中创建变量并运算
3.7 Add函数栈帧的销毁
3.8 返回main函数栈帧
一、前言
在C语言的过程中,我们心中难免有一些懵懂的地方。譬如:
1.局部变量到底是怎么在栈上创建的?
2.为什么局部变量不初始化为随机值?
3.函数是怎么传参的?传参的先后顺序是什么?
4.形参和实参是什么关系?
5.函数调用是怎么实现的?
6.函数调用后是怎么返回的?
学习完函数栈帧的创建与销毁,想必就能有一个比较清晰的认识了
本文的调试结果是使用vs2013和vs2019反汇编调试而出的,不同编译器函数栈帧的创建销毁过程略有不同,具体细节取决于编译器的实现。(越高级的编译器封装越好,越不易观察学习)
二、基础知识介绍
2.1 寄存器介绍
寄存器名称 | 功能 |
eax | 累加器,是多数加法乘法指令的缺省寄存器 |
ebx | 基地址寄存器,在内存寻址时存放基地址 |
ecx | 计数寄存器,用于循环操作,比如重复的字符存储操作,或者数字统计 |
edx | 作为eax的溢出寄存器,总是被用来放整数除法产生的余数 |
esi | 源变址寄存器,主要用于存放存储单元在段内的偏移量 通常在内存操作指令中作为“源地址指针”使用 |
edi | 目的变址寄存器,主要用于存放存储单元在段内的偏移量 |
eip | 控制寄存器,存储CPU下次所执行的指令地址(存放指令偏移地址) |
esp | 栈指针寄存器(extended stack pointer),其内存放着一个指针。 该指针永远指向栈最上面一个栈帧的栈顶。esp用于堆栈操作,被形象地称为栈顶指针。 |
ebp | 基址指针,指栈的栈底指针。基址指针寄存器(extended base pointer)。 一般与 |
2.2、汇编指令介绍
指令 | 名称 | 作用 |
push | 进栈指令 | 将源操作数压入栈中 |
pop | 弹栈指令 | 从栈中弹出双字或字数据至目的操作数中 |
mov | 传送指令 | 把一个字节、字、或双字从源操作数传送至目的操作数 |
add | 加法指令 | 目的操作数加源操作数,结果送至目的操作数 |
sub | 减法指令 | 目的操作数减源操作数,结果送至目的操作数 |
lea | 取有效地址指令 | 将源操作数的有效地址传送到通用寄存器 |
call | 过程调用指令 | 程序下一条指令的位置的地址压入堆栈中,并转移到调用的子程序 |
ret | 段内过程返回指令 | 从调用过程返回,继续执行主程序。 |
jmp | 无条件转移指令 | 使程序无条件地转移到指令规定的目的地址去执行指令 |
字(word):表示两个字节长度的数值 双字(dword):表示四个字节长度的数值
三、函数栈帧的创建销毁过程
接下来使用下述代码进行演示 (语句简单,易于观察)
#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;
}
3.1 调用main函数的函数
vs2013进入调试,打开窗口中的调用堆栈,F10不断调试,得到下图
可以发现main函数也是由别的函数(__tmainCRTStartup函数)调用的,而__tmainCRTStartup函数则是由mainCRTStartup函数调用的。
3.2 main函数开辟栈帧
3.3 在main函数中创建变量
在main函数开辟的栈帧中创建变量