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
的需要两个参数, x
和 y
, 传递参数时, 从右往左依次压入栈中
add stack frame |
---|
x(add) = 1 |
y(add) = 2 |
然后我们执行函数体的内容, 即a + b
, 结果将保存在寄存器 eax
中.
当 add
函数执行完毕后, 依次将 x, y 弹出栈, 这一步即是释放函数的空间, 然后取到程序的返回地址 return 0 的地址
修改 pc 的值, 使其指向 return 0 的地址
, 然后继续执行 main
函数的剩余部分.