学习笔记(三)深入理解 “栈” —— 栈的具体应用

栈在程序调用子程序时的使用方式是由编译器和硬件自动管理的,程序员通常不需要手动操作栈,但理解其工作方式有助于更好地设计和调试代码。以下是栈在函数调用中的具体使用步骤:

1. 主程序调用子程序时的栈操作

当主程序调用子程序时,栈的使用大致可以分为以下几个步骤:

1.1 压入返回地址
  • 主程序在调用子程序时,CPU会将**主程序的下一条指令地址(返回地址)**压入栈中,确保子程序执行完后,能够返回到正确的位置继续执行主程序。
  • 这是栈的第一步操作,它记录了函数调用前的执行点。
1.2 传递参数
  • 如果子程序需要接收参数,这些参数也会被压入栈,子程序通过从栈中读取这些参数来进行操作。
  • 不同编译器的实现方式可能不同,有时参数会通过寄存器传递,但对于复杂的调用情况,参数往往放入栈中。
1.3 压入局部变量和状态
  • 子程序可能会声明自己的局部变量,这些局部变量存储在栈上。每次子程序被调用时,栈会为其分配一个新的区域用于存储局部变量,防止与其他函数的局部变量混淆。
  • 子程序还会保存当前寄存器状态,以确保子程序修改寄存器后,不影响主程序的正常执行。
1.4 执行子程序
  • 当所有参数和局部变量都已经压入栈后,子程序开始执行。
  • 子程序可以嵌套调用其他子程序,这时栈上会继续分配新的空间来存储新的返回地址、参数和局部变量。

2. 子程序返回主程序时的栈操作

当子程序执行完毕并返回主程序时,栈会反向操作,弹出信息:

2.1 恢复局部变量和寄存器
  • 子程序执行完毕后,局部变量的栈空间会被释放。栈指针移动,释放这些局部变量的空间。
  • CPU从栈中恢复寄存器状态,保证主程序的继续执行不会受到影响。
2.2 弹出返回地址
  • 子程序从栈中弹出返回地址,并跳回到主程序的这一位置继续执行。
  • 这个返回地址是之前主程序调用子程序时压入栈中的,确保子程序执行后能正确返回主程序的调用点。

3. 多次嵌套调用时的栈管理

  • 如果主程序调用多个子程序,或者子程序之间互相嵌套调用,栈的先入后出机制可以确保调用顺序正确。
  • 每次调用新的子程序时,都会将当前的返回地址和上下文信息压入栈,等到最内层的子程序执行完毕后,栈会按照先入后出的顺序依次弹出这些返回地址和变量,逐步返回到上一层函数。

实际代码中的示例

虽然栈的管理通常由编译器和硬件负责,但我们可以通过编写递归函数来感受栈的使用:

#include <stdio.h>

// 递归函数来演示栈的使用
int factorial(int n) {
    if (n == 1) {
        return 1;  // 递归基准条件
    }
    return n * factorial(n - 1);  // 递归调用
}

int main() {
    int result = factorial(5);
    printf("Factorial of 5 is: %d\n", result);
    return 0;
}
执行过程:
  1. main()函数调用factorial(5),栈上保存了main()的返回地址。
  2. factorial(5)调用factorial(4),栈上保存了factorial(5)的返回地址,依此类推直到调用factorial(1)
  3. factorial(1)返回时,栈开始逐层弹出,直到返回到main(),最终打印结果。

每次递归调用都会将当前的返回地址、参数和局部变量压入栈,递归完成后再依次弹出返回值。

总结:

  • 栈的具体使用是在函数调用返回过程中自动进行的。
  • 栈会按顺序存储返回地址、参数、局部变量,并在函数完成后依次弹出,确保程序能按正确的顺序执行。
  • 通常你不需要手动管理栈,但理解其工作机制对编写和优化代码非常有帮助。

PS:创作不易 看完如果觉得有用的话麻烦 点赞+关注+收藏 再走吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值