递归函数每次调用都在栈上创建独立栈帧,保存参数、局部变量和返回地址;栈帧按lifo顺序压入弹出,实现嵌套计算;过深递归导致栈溢出,尾递归可优化为o(1)空间复杂度。

递归函数执行时,每调用一次自身,系统就在栈上创建一个新的栈帧。这个栈帧保存着当前调用的参数、局部变量、返回地址等信息。所有栈帧按调用顺序依次压入栈中,形成“层层嵌套”的结构;当递归到达终止条件后,再按后进先出(LIFO)顺序逐个弹出并计算返回值。
栈帧是函数的独立执行环境
每个函数调用都会在内存栈区分配一块独立空间,即栈帧。它不是共享的,而是隔离的:即使递归调用的是同一个函数名,每次调用的参数、局部变量都各自保存在自己的栈帧里,互不干扰。比如 fact(5) 和 fact(4) 各自拥有独立的形参 n,一个存 5,一个存 4。
栈帧中通常包含:
- 函数的形参值
- 函数内部定义的局部变量
- 调用结束后要跳转回去的返回地址
- 被调用函数可能修改的寄存器备份(如某些 ABI 要求)
递归展开过程就是不断压栈
以计算阶乘 fact(3) 为例:
- 调用 fact(3) → 创建栈帧 #1,n=3,执行到 return 3 * fact(2),但此时不能立刻算出结果,必须等待 fact(2) 返回
- 于是调用 fact(2) → 在栈顶新建栈帧 #2,n=2,同样卡在 return 2 * fact(1)
- 接着调用 fact(1) → 新建栈帧 #3,n=1,继续调用 fact(0)
- 调用 fact(0) → 新建栈帧 #4,满足终止条件(如 if (n == 0) return 1),直接返回 1
此时栈中已有 4 个活跃栈帧,从底到顶分别是:fact(3)、fact(2)、fact(1)、fact(0)。
回退计算依赖栈的后进先出特性
栈帧不会自动消失,只有执行完并返回后才被释放。所以 fact(0) 返回 1 后,栈帧 #4 弹出,控制权回到栈帧 #3(fact(1));它用这个 1 算出 1 * 1 = 1,然后返回并弹出;接着 fact(2) 得到 1,算出 2 * 1 = 2;最后 fact(3) 得到 2,算出 3 * 2 = 6。
整个过程就像下楼再逐级上楼:每一层都等着下一层的答案,而栈天然支持这种“暂存-等待-取回”的机制。
栈溢出和尾递归优化的关键点
如果递归太深(比如计算 fact(100000)),栈帧会持续累积,超出系统分配的栈空间上限,导致栈溢出(Stack Overflow)。这是递归最典型的运行时风险。
尾递归是一种特殊形式:函数的最后一步操作是调用自身,且 return 后不带额外运算(例如 return fact_tail(n-1, n * acc))。这时编译器可复用当前栈帧,把新参数覆盖进去,避免新增栈帧——本质上把递归转成了循环,空间复杂度从 O(n) 降到 O(1)。
但普通递归(如 return n * fact(n-1))无法做此优化,因为乘法必须等子调用返回后才能执行,栈帧必须保留。











