C语言中函数栈帧的创建和销毁的深层分析
什么是函数栈帧
在C语言中,每当一个函数被调用时,系统会在当前线程的栈上为该函数创建一个栈帧(Stack Frame),用于保存该函数调用时的现场信息(如首地址、传递参数、局部变量等信息)。函数栈帧的创建和销毁是函数调用的必要过程,也是C语言程序的基本运行机制之一。
函数栈帧的创建过程
函数栈帧的创建过程分为以下几个步骤:
- 参数入栈:函数调用时,参数须按照约定入栈。在一些体系结构中,参数入栈的顺序是从右到左。
- 栈帧开辟:栈指针(SP)指向当前栈顶,将返回地址(或是跳转地址)入栈;将基地址(BP)更新为当前SP,此时,BP指向当前栈帧的基地址。
- 局部变量入栈:将函数中所需使用的局部变量按顺序入栈。
- 函数栈帧创建完毕:此时,即刻可以通过BP指针访问传递的参数和局部变量。
举个例子:
void func(int a, int b) {
int c = a + b;
printf("%d", c);
}
int main() {
func(1, 2);
return 0;
}
当调用func函数时,参数a和b会先入栈,然后func函数开辟一个新的栈帧,保存当前现场信息(如地址和寄存器状态),并将基地址BP指向此时的栈顶;局部变量c也会入栈,此时,函数栈帧创建完毕。
函数栈帧的销毁过程
当函数执行结束后,系统需要销毁栈帧,恢复到先前的状态。函数栈帧的销毁过程可以分为以下几个步骤:
- 返回值传递:将函数返回值传递给调用该函数的函数。若返回值为结构体或被传递引用,则需要在堆上生成该对象然后将其首地址返回给调用者。
- 局部变量出栈:将局部变量按照倒序(即与入栈的顺序相反)出栈。
- 栈帧清理:还原当前现场信息(如地址和寄存器状态),将原来的BP指针还原为当前的SP指针。
- 跳转指令:将返回地址从栈中弹出并跳转到对应的位置。
继续上面的例子:
void func(int a, int b) {
int c = a + b;
printf("%d", c);
}
int main() {
func(1, 2);
return 0;
}
当func函数执行结束后,将会按照如下步骤销毁函数栈帧:
- 函数将执行完毕并将返回值(如果有的话)传递到栈顶。
- 先将局部变量c出栈。
- 在还原栈帧并将原始BP指针(也就是func第一步操作时压栈时的地址)还原到当前SP位置上。
- 跳转回调用func函数时的位置。
示例说明
示例一
代码:
int add(int a, int b) {
int c = a + b;
return c;
}
int main() {
int result = add(1, 2);
return 0;
}
在执行add函数时,参数a和b会先入栈;然后add函数会开辟一个新的栈帧,并将基地址(BP)更新为当前栈顶;最后将局部变量c入栈,函数栈帧创建完成。接着,程序执行return语句,将返回值c传递给调用函数,然后函数开始销毁栈帧: 先将局部变量c出栈, 在还原函数栈帧,最后执行ret指令,跳回到调用add函数时的位置。
示例二
代码:
int div(int a, int b) {
if(b != 0) {
return a / b;
} else {
printf("Error: Division by zero.");
return 0;
}
}
int main() {
int a = 5;
int b = 0;
int result = div(a, b);
return 0;
}
在执行div函数时,参数a和b会先入栈;然后div函数会开辟一个新的栈帧,并将基地址(BP)更新为当前栈顶;在div函数调用if条件判断时,由于b的值为0,程序执行else语句中的printf函数,输出错误信息。接着,程序执行return语句,将返回值0传递给调用函数,然后函数开始销毁栈帧: 先将局部变量(也就是printf函数中的“Error: Division by zero.”字符串)出栈, 在还原div函数的栈帧,最后执行ret指令,跳回到调用div函数时的位置。
以上就是函数栈帧的创建和销毁的深层分析。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C语言中函数栈帧的创建和销毁的深层分析 - Python技术站