大家好,我是真的王。今天我来分享一下有关栈帧的知识。
目录
前言
我们每个人编写代码时都会调用函数,但你知道调用函数的具体过程吗?
下面以vs2019环境为例演示。
一、寄存器
下面引用维基百科对寄存器的定义”
寄存器(Register)是中央处理器内用来暂存指令、数据和地址的电脑存储器。寄存器的存贮容量有限,读写速度非常快。在计算机体系结构里,寄存器存储在已知时间点所作计算的中间结果,通过快速地访问数据来加速计算机程序的执行。
寄存器位于存储器层次结构的最顶端,也是CPU可以读写的最快的存储器,事实上所谓的暂存已经不像存储器,而是非常短暂的读写少量信息并马上用到,因为通常程序执行的步骤中,这期间就会一直使用它。寄存器通常都是以他们可以保存的比特数量来计量,举例来说,一个8位寄存器或32位寄存器。在中央处理器中,包含寄存器的部件有指令寄存器(IR)、程序计数器和累加器。寄存器现在都以寄存器数组的方式来实现,但是他们也可能使用单独的触发器、高速的核心存储器、薄膜存储器以及在数种机器上的其他方式来实现出来。”
寄存器我们只要大致了解通用寄存器即可
- EAX:(针对操作数和结果数据的)累加器 ,返回函数结果
- EBX:(DS段中的数据指针)基址寄存器
- ECX:(字符串和循环操作数)计数器
- EDX:(I/O指针)数据寄存器
- EBP:(SS段中栈内数据指针)扩展基址指针寄存器
- ESI:(字符串操作源指针)源变址寄存器
- EDI:(字符串操作目标指针)目的变址寄存器
- ESP:(SS段中栈指针)栈指针寄存器
其中ebp,esp 在本文中较为重要
ebp(栈底指针)和esp(栈顶指针)负责维护调用的函数
二、具体步骤
1.内存
我们要弄清楚函数调用的具体过程,我们就要了解一下内存
一个内存单元是1个字节(1个字节8个比特位)
我们或许听说过电脑有32位的也有64位的,32位就是有32根地址线,64位同理。而地址线就是实际在电脑中的线,它们有高电平和低电平两种状态,已32位为例,每一根地址线有两种状态,它一共有2的32次方种状态,也就可以储存2的32次方比特的数据。
我们就可以知道为什么我们每次取地址时,都是随机的。
函数在栈区开辟,栈这种数据结构,类似于弹夹
栈底
我们每次创建临时变量时都会将变量从栈顶向下压,形象的称为压栈
2.程序执行的具体过程
接下来,我们进入正题!!!!!
我们以最简单的代码为例
#include<stdio.h>
int add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 3;
int b = 4;
int c=add(a, b);
printf("%d\n", c);
return 0;
}
首先我们在vs2019环境下,打开调试窗口点击调试,窗口,反汇编
这时,我们来到反汇编。我们可以看到push(压栈),mov(移动),rep(初始化),call(调用)
pop(出栈)等汇编代码。(函数在被调用前,编译器会预先在栈区开辟一块空间将这块空间赋值成CCCCCCCCCCC......这也就是为什么某一个变量没有初始化时出现的烫烫烫的原因。
我们发现在还没有进入main函数之前系统进行了一系列过程
main函数并不是一个程序最开始执行的函数。
我们再打开内存,调用堆栈
发现main不是最初调用的函数,main()
函数是被__tmainCRTStartup
函数调用的,而 __tmainCRTStartup
又是被mainCRTStartup
调用的。
我们再回到代码
在创建a时之前,先把ebp压入栈中,然后移动ebp,esp
这段代码的含义为,esp的值减少4字节,将ebp的值压入栈中,将esp的值赋给ebp,将esp-0E4h字节的结果保存在esp中(esp指针向低地址方向移动0E4h字节)。
esp的值减少4字节,ebx的值压入栈中。esp的值减少4字节,esi的值压入栈中,esp的值减少4字节,将edi的值压入栈中。
3.调用add()函数
首先栈区会存放call命令的下一条指令的地址,ebp会减去某个数值来到add()函数的栈帧,随着函数内部指令的执行,ebp逐渐递减,来到add()函数的栈顶,这时ebp指向add()函数返回值的地址。寄存器预先存放的地址就是通过ebp指针,返回到call命令的下一条指令的地址,然后add()函数栈帧销毁。而到程序的末尾main()函数的最后也是经过上述过程return 0和栈帧销毁的。
总结
以上就是函数栈帧相关的知识分享,如有错误请指正。谢谢