文章

C语言 函数栈的工作原理原理

C语言 函数栈的工作原理原理

在工作过程中, 由于遇到了在C++与C语言混用的场景中, 使用longjmp进行跳转时, 出现栈溢出的问题. 因此, 对函数栈的工作原理和longjmp的原理和实现进行了学习. 本文只记录函数栈的工作原理, longjmp的原理和实现将在后续文章中记录.

函数栈的工作原理

函数栈是一个后进先出(LIFO)的数据结构, 用于存储函数调用过程中的局部变量和函数返回地址. 当一个函数被调用时, 它的局部变量和返回地址被压入栈中, 当函数返回时, 这些值被弹出栈.

例如有如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
int add(int x, int y);

int main() {
    int a = 1;
    int b = 2;
    add(a, b);
    return 0;
}

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

程序计数器

我们程序的代码实际上是被保存在一段连续的内存空间中的, 为了保证代码一行行的连续执行, 计算机需要有一个专门的指针指向下一条要执行的代码的地址, 即程序计数器 (program counter, pc).

如果我们要将代码跳转到某个位置, 只需要修改 pc 的值为目的地址即可.

运行时栈

计算机会有一个专门的指针指向栈顶, 即栈顶指针 (stack pointer, sp), 此时栈顶指针指向return 0 的地址, 栈指针以下的内容都是main函数的栈帧.

在执行main函数时, 从上往下依次执行, 计算机在栈中开辟空间, 依次将main函数的局部变量压入栈中

main stack frame
b(main) = 2
a(main) = 1

当执行到调用add函数时, 计算机首先会将add函数紧跟的地址 (即’main’函数中’add’函数调用后的下一条指令’return’的地址) 压入栈中

main stack frame
return 0 的地址
b(main) = 2
a(main) = 1

接下来是传递参数, 函数 add 的需要两个参数, xy, 传递参数时, 从右往左依次压入栈中

add stack frame
x(add) = 1
y(add) = 2

然后我们执行函数体的内容, 即a + b, 结果将保存在寄存器 eax 中.

add 函数执行完毕后, 依次将 x, y 弹出栈, 这一步即是释放函数的空间, 然后取到程序的返回地址 return 0 的地址

修改 pc 的值, 使其指向 return 0 的地址, 然后继续执行 main 函数的剩余部分.

本文由作者按照 CC BY 4.0 进行授权