栈帧原理介绍

基础知识

进程中的栈区(stack)用于维护函数调用的上下文,没有栈就没法实现函数调用。栈用于存放函数(包括main函数)里的局部变量,函数调用时要传递的参数等。

在进程的内存空间中,栈是向下生长的,即栈底在高地址,栈顶在低地址,栈底固定住,栈从内存高地址->低地址的路径延伸。

栈帧(stack frame):每一次函数调用时,都会在栈上为这次函数调用维护一个独立的栈帧。一个栈帧由两个寄存器来界定:

  • ebp:指向栈底的指针,称为帧指针(Frame Pointer)
  • esp:指向栈顶的指针,称为栈指针(Stack Pointer)

进程的用户空间中存放数据的地方主要包括数据段(静态变量和全局变量)、堆和栈。

其中只有数据段是可以在编译期间就明确计算出来所需空间大小的,并且不会再改变,因此它们可以直接存放在可执行文件的特定的节里(而且包含初始化的值),程序运行时直接将这个节加载到特定的段中,不必在程序运行期间用额外的代码来产生这些变量。参考关于书上说的“编译的时候分配内存”

堆和栈的内存空间都是在程序运行期间分配的。

栈帧结构图

image-20210902213330592

函数调用和返回

以一个程序为例子:

 #include<stdio.h>

int sum(int *a, int *b) {
    int c;
    c = *a + *b;
  	return c; 
}

int main() {
    int a, b;
    a = 16; b = 32;
    sum(&a, &b);

    printf("%d\n", (a-b));
    return (a-b);
}

对应的栈帧结构图:

这里写图片描述

main函数运行过程中栈帧变化:

  1. 压栈局部变量:按照定义顺序依次将局部变量a、b压栈。
  2. 压栈函数参数:接下来准备调用函数sum,依次将变量b和a的地址压栈。
  3. 压栈返回地址:函数参数压栈完毕后,再将返回地址压栈,这里的返回地址就是main函数里这一条跳转指令的下一条指令的地址,以便函数调用完毕之后拿出这个返回地址继续往下运行。

这里局部变量和函数参数的压栈顺序也有讲究

  • 函数里的局部变量的压栈顺序:编译器无溢出保护机制时正向顺序压栈,有溢出保护机制时则相反,先定义的后压栈。参考局部变量申请栈空间时的入栈顺序
  • 函数调用时参数的压栈顺序:也跟编译器的实现方式有关,Pascal语言从左往右入栈,C语言从右往左入栈,主要是因为前者不支持可变长参数,而后者支持函数可变长参数(就是后面参数是省略号的形式),如果C语言不支持这个可变长参数的特色,那么其实也可以左序入栈。参考C语言中函数参数入栈的顺序

至此,main函数的栈帧就到界了,接下来开始构建sum函数的栈帧。

  1. 压栈ebp:将帧指针ebp入栈(main栈帧的基地址)
  2. 更新ebp:将帧指针ebp更新为当前的栈指针esp,此时帧指针ebp指向sum栈帧的基地址。
  3. 执行函数操作:压栈局部变量c,可以通过esp指针的偏移来读到传过来的参数,然后运行加法等。
  4. 保存返回值:将返回值保存到eax寄存器中。

开始函数返回

  1. 更新esp:栈指针esp到值设置为帧指针ebp的值(即当前函数栈帧的基地址),销毁sum函数栈帧。
  2. 更新ebp:pop出main栈帧的ebp值,将帧指针ebp寄存器更新为这个旧栈帧的基地址。
  3. ret:此时返回地址暴露了出来,pop出来并且后续会跳转到返回地址处继续执行那一条指令(比如说从eax寄存器取出返回值值赋给一个变量之类的)。
  4. 清理函数参数:此时main栈帧里给sum函数存储的参数已经不需要了,因此将esp上移,将这些参数出栈。

此时main栈帧又恢复了函数调用前的状态,可以继续往下运行了。

others

相关汇编指令:

  • push:压栈
  • pop:出栈
  • call:把返回地址(程序中紧随调用指令call后面一条指令的地址)入栈,并跳转到被调用函数开始处执行。
  • ret:(经过多次pop之后,esp指向调用者栈桢的顶部,也就是存放返回地址的地方)此时调用ret指令可弹出栈顶处的地址并跳转到该地址处。ret实际上是由汇编指令leave来实现的。

leave指令由下面两个指令组成

mov %ebp, %esp # 恢复原esp的值(esp=ebp,esp此时指向被调用者栈桢的开始处)
pop %ebp       # 恢复原ebp的值(esp=esp+4,它执行了调用者的返回地址处,ebp也指向了调用者的栈底)

参考文章

函数调用过程中函数栈详解

栈帧(Stack Frame)

函数调用

c函数调用过程原理及函数栈帧分析

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值