Bootstrap

基于vscode环境,浅析C程序函数调用的栈内存分布

前言

在程序执行的过程中,每一行代码背后都隐藏着复杂的内存操作。理解函数调用栈如何管理这些变化,对于每一个程序员而言都是至关重要的技能。本文将结合vscode分析函数调用栈,揭示程序如何动态地管理内存,从而帮助我们更高效地调试和优化代码。本文主要结合了函数调用栈的相关博客和视频,并加入自己的理解。

0.注意

(1)本文的环境为windows+mingw gcc,使用的IDE为vscode。
(2)不同的编译器的汇编有所不同。
(3)本文严格按照汇编来理解函数调用的栈分布

1.相关寄存器简述

寄存器名称寄存器描述
eax主要用于算术运算和数据传输操作
ecx通常用于计数或传参
edx通常用于传递参数或辅助计算
rip存储着处理器将要执行的下一条指令的地址
rbp在函数调用中用作基址指针
rsp指示了堆栈的顶部位置

2.代码示例

#include<stdio.h>

int add(int a, int b){
    return a+b;
}

int main(void){
    int a = 1;
    int b = 2;
    int c = add(a,b);
    printf("%d\n",c);
    return 0;
}

3.结合vscode汇编图解

3.1 步骤1 主函数准备传入参数

main函数:

    int a = 1;
    int b = 2;

在这里插入图片描述

通过以上汇编可以看出,main栈中在rbp基地址高4字节和8字节的位置存储了a和b变量;
为了调用add函数,最终将a和b分别传入到ecx和edx寄存器
栈分布如下:

在这里插入图片描述

3.2 步骤2 保存现场

call调用会将rip推向栈,即将函数调用的下一个指令保存到栈中,以便函数返回时能接着往下执行指令。汇编如下。
在这里插入图片描述
栈分布如下:
在这里插入图片描述

3.3 步骤3 保存形参并计算

根据以下汇编,可以看出这几步骤主要讲main传递来的形参进行保存,然后取出进行计算。
在这里插入图片描述
栈分布如下:

在这里插入图片描述

3.4 步骤4 恢复现场

步骤3已经完成了数据的运算并将数据存储到eax中,是时候该返回数据给main函数了。
pop rbp:将栈顶弹出并赋值给rbp,这样rbp就恢复到main栈的rbp。
ret:将栈顶元素弹出并赋值给rip,因为此时栈顶为调用指令为步骤2中保存的调用指令下一个地址,所以此时rip就回到main函数中调用add函数后的指令,main函数得以继续向下执行。
返回汇编如下:
在这里插入图片描述
栈分布如下:

总结

每一次函数调用的背后,都有一个精心设计的内存舞台,等待着我们去探索和驾驭。

;