函数栈帧的创建与销毁(最详细的一集)上

前言

1.我们在进行c语言代码编程的时候,常常会把独立的一个功能抽象为函数,利用函数去实现各种的功能,那么,函数是如何调用的?函数的返回值是怎么返回的?参数又是如何传参的?所有这些问题都会跟函数栈帧问题有关。

正片开始:

1.什么是函数栈帧

具体来说,函数栈帧就是在函数调用过程中在程序的调用栈开辟的空间,而这些空间就用来存放:

1.函数参数和函数的返回值

2.临时变量(包括函数的非静态的局部变量一集编译器自动产生的其他临时变量)

3.保存上下文信息(包括函数调用前后需要保持不变的寄存器)

2.理解了函数栈帧后能解决什么问题?

我认为理解这些是一种修炼”内功“的行为,在理解了函数栈帧的创建与销毁后,以下的问题便迎刃而解了

1.局部变量是怎么在栈区中创建的

2.为什么局部变量不出货内容是随机的?

3.函数调用时参数是怎么传递的?传递的顺序是怎么样的?

4函数的实参和形参是怎么分别实例化的

函数栈帧的创建和销毁解析

栈(stack)是现代计算机中最重要的概念之一,每一个程序都用到了栈,没有栈就没有函数,也就没有各种计算机语言,

栈被定义为一种特殊的容器,我们也会在数据结构中学到栈,用户可以将数据压入栈(push),也可以将入栈的数据弹出(pop),但是必须遵守的一个规则是:先入后出,先入栈的数据后出栈(就跟弹匣一样,压入子弹,射出子弹)

在计算机系统中,栈是一个具有以上属性的动态内存区域,程序可以将数据压栈,也可以将数据出栈,压栈会让栈增长,出栈会让栈减小。

在经典的操作系统,栈总是向下增长的(高地址向低地址)

在我们常见的i386或者X86-64下,栈顶是由esp的寄存器进行定位(涉及汇编指令)。

相关寄存器和汇编指令

相关寄存器

eax:通用寄存器,保留临时数据,常用于返回值

ebx:通用寄存器,保留临时数据

ebp:栈底寄存器

esp:栈顶寄存器

eip:指令寄存器,保存当前指令的下一条指令的地址

rbp:是 x86-64 架构中的寄存器,它在汇编语言中通常用于构建函数调用栈帧(stack frame)。

rdi:是 x86-64 架构中的通用寄存器之一。在函数调用中,rdi 通常用于传递函数的第一个参数。在 x86-64 架构中,函数的前六个参数(整数或指针类型)通常通过 rdirsirdxrcxr8r9 这六个通用寄存器传递

相关汇编命令

mov:数据转移指令

push:数据入栈,同时esp栈顶寄存器也要发生改变

pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变

sub:减法命令

add:加法命令

call:函数调用,1. 压入返回地址 2. 转入目标函数

jump:通过修改eip,转入目标函数,进行调用

ret:恢复返回地址,压入eip,类似pop eip命令

lea:用于加载有效地址(Load Effective Address)。虽然 lea 的名称中包含 "load" 一词,但它实际上并不执行内存加载操作。相反,lea 计算一个有效地址,并将结果存储到目标操作数中

解析函数栈帧的创建与销毁

1.预备知识

1.在每一次的函数调用中,都需要为本次函数调用开辟空间,也就是函数栈帧的空间。

2.这块空间的维护是用的两个寄存器,esp和ebp,ebp记录栈底地址,esp记录栈顶地址。

如图

在一个函数的调用过程中,会出现以下的过程,这里举一个简单的例子说明

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

以上我举了一个简单的加法函数,我们可以用VS(观测函数栈帧使用越低的版本越容易观察

我们打开VS调试的反汇编,查看相对应的汇编代码

 
int main()
{
00007FF7C47518D0  push        rbp  (首先把rbp寄存器压栈)
00007FF7C47518D2  push        rdi  push rdi 表示将 rdi 的当前值保存到栈上。这是为了在函数执行期间保护 rdi 的值,以便在后续的代码中使用)
00007FF7C47518D3  sub         rsp,148h   (减去rsp的空间,表示将栈指针 rsp 的值减去 148h(也可以写作 0x148328),即在栈上分配 328 字节的空间)
00007FF7C47518DA  lea         rbp,[rsp+20h]  (将rsp加上20h的空间,也就是分配“32”字节的偏移量
00007FF7C47518DF  lea         rcx,[__A7141C24_test@c (07FF7C4761008h)]  (加载一个地址到“rcx”寄存器)
00007FF7C47518E6  call        __CheckForDebuggerJustMyCode (07FF7C4751370h)  
    int a = 1;(调用函数,这里因为编译器优化的原因,在 这里就i已经知道 a是下面Add函数的参数,所以Add 函数的调用移到初始化 a 的位置
00007FF7C47518EB  mov         dword ptr [a],1  (变量声明初始化a,将常量1存储到a)
    int b = 2;
00007FF7C47518F2  mov         dword ptr [b],2  (变量声明初始化b,将常量2存储到b)
    int ret = Add(a, b);
00007FF7C47518F9  mov         edx,dword ptr [b]  (将变量B存进edx寄存器)
00007FF7C47518FC  mov         ecx,dword ptr [a]  (将变量A存进ecx寄存器)
00007FF7C47518FF  call        Add (07FF7C4751348h)  (调用Add函数)
00007FF7C4751904  mov         dword ptr [ret],eax  
    printf("%d", ret);
00007FF7C4751907  mov         edx,dword ptr [ret]  
00007FF7C475190A  lea         rcx,[string "%d" (07FF7C4759C24h)]  
00007FF7C4751911  call        printf (07FF7C4751195h)(call调用printf函数)  
    return 0;
00007FF7C4751916  xor         eax,eax  
}

  • 25
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值