【深入理解函数栈帧:探索函数调用的内部机制】

简介: 【深入理解函数栈帧:探索函数调用的内部机制】

本章重点


  • 了解汇编指令
  • 深刻理解函数调用过程


样例代码:


#include <stdio.h>
int MyAdd(int a, int b)
{
  int c = 0;
  c = a + b;
  return c;
}
int main()
{
  int x = 0xA;
  int y = 0xB;
  int z = MyAdd(x, y);
  printf("z = %x\n", z);
  return 0;
}


C语言地址空间学习



  • 代码段:存储程序的机器指令,包括函数的二进制代码。
  • 字符常量区:存储字符串常量和字符常量,这些值在程序中是只读的,不可修改。
  • 已初始化变量区:存储已经初始化的全局变量和静态变量。
  • 未初始化变量区:存储未初始化的全局变量和静态变量,在程序加载时会被初始化为默认值(如0)。
  • 堆:动态分配的内存区域,用于存储动态分配的变量、数据结构和对象。它的大小和位置在程序运行时动态调整。
  • 栈:存储函数调用的局部变量、函数参数、函数返回地址以及其他与函数调用相关的信息。栈是一种后进先出(LIFO)的数据结构,函数调用时会在栈上创建一个新的帧,函数返回时会将该帧从栈中弹出。


认识相关寄存器


  • eax:通用寄存器,保留临时数据,常用于返回值
  • ebx:通用寄存器,保留临时数据
  • ebp:栈底寄存器 esp:栈顶寄存器
  • eip:指令寄存器,保存当前指令的下一条指令的地址


认识相关汇编命令


  • mov:数据转移指令
  • push:数据入栈,同时esp栈顶寄存器也要发生改变
  • pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
  • sub:减法命令
  • add:加法命令
  • call:函数调用,1. 压入返回地址 2. 转入目标函数
  • jump:通过修改eip,转入目标函数,进行调用
  • ret:恢复返回地址,压入eip,类似pop eip命令


讲解思路图


备注: vs编译器有栈随机化的处理,所以每次看到的相关数据可能会不太一致,不过我们重点关注变化原理,弱化数据。


1、起步,main函数也是要被调用的



2、main函数也是要形成栈帧结构的



3、变量x和入栈



4、临时变量的入栈拷贝



6、开始调用函数



7、MyAdd函数栈帧形成



8、变量c入栈并完成加法



9、寄存器eax保存返回值



10、释放MyAdd函数栈帧



11、ret返回



12、函数参数的临时变量被销毁, 程序已经回到main函数栈帧,并且已经将寄存器eax的值给到z变量。



总结:


  1. 调用函数,需要先形成临时拷贝,形成过程是从右向左的
  2. 临时空间的开辟,是在对应函数栈帧内部开辟的
  3. 函数调用完毕,栈帧结构被释放掉
  4. 临时变量具有临时性的本质:栈帧具有临时性
  5. 调用函数是有成本的,成本体现在时间和空间上,本质是形成和释放栈帧有成本
  6. 函数调用,因拷贝所形成的临时变量,变量和变量之间的位置关系是有规律的


根据第六点,我们可以发现一个现象


#include <stdio.h>
void MyAdd(int a, int b)
{
  //由于临时变量是从右往左创建的
  //所以b的地址高于a
  printf("before:%d\n", b);
  *(&a + 1) = 100;
  printf("after:%d\n", b);
}
int main()
{
  int x = 0xA;
  int y = 0xB;
  MyAdd(x, y);
  return 0;
}



我们上面的代码能很好的体现函数参数的地址位置关系,但这是一种不可预测和不可靠的行为。


临时变量的内存布局是由编译器决定的,这是未定义行为。不同的编译器可能采用不同的策略和规则来分配内存。因此,不能依赖于特定的内存布局来进行编程。


临时变量在栈上分配内存,通常是从高地址向低地址分配。因此,b的地址可能比a的地址更高。然而,这并不意味着b的地址正好位于a的下一个内存位置。



相关文章
|
7月前
|
存储 安全 C语言
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2
|
7月前
|
存储 编译器 C语言
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1
|
4月前
|
存储 C语言
【C语言】——函数栈帧的创建与销毁
【C语言】——函数栈帧的创建与销毁
|
7月前
|
存储 程序员 编译器
深入理解函数调用--函数栈帧
深入理解函数调用--函数栈帧
|
7月前
|
存储 编译器 C语言
C语言:底层剖析——函数栈帧的创建和销毁
C语言:底层剖析——函数栈帧的创建和销毁
|
程序员 编译器 C语言
细谈函数栈帧的创建与销毁
我们在写C语言代码时,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。那函数如何调用?函数的返回值如何返回的?函数参数是如何传递的?这些问题都与函数栈帧有关系。
184 0
|
存储 编译器 C语言
C语言——详解函数栈帧的创建和销毁
C语言——详解函数栈帧的创建和销毁
|
存储 安全 编译器
【C语言】函数栈帧的创建和销毁(一)
【C语言】函数栈帧的创建和销毁.
88 0
【C语言】函数栈帧的创建和销毁(一)
|
C语言
C语言——函数栈帧的创建和销毁(一)
C语言——函数栈帧的创建和销毁
|
安全 编译器 C语言
C语言——函数栈帧的创建和销毁(二)
C语言——函数栈帧的创建和销毁(二)