有没有想过,主函数在执行时,计算机底层是如何保证执行顺序的?嵌套调用的函数,又是怎么维持调用次序的?
在各类语言的底层,函数执行顺序依靠调用栈来保障。每一次函数调用都会生成一个栈帧,栈帧保存着函数运行的关键信息:
第一,返回地址,也就是上一层调用指令的地址;
第二,参数,返回值的存储空间。
每次函数执行完毕后,上层就可以从这个空间拿到当前函数的执行结果,回到上一层继续执行。
前面提到栈里保存的返回地址,是第二层保障。栈的设计,让 CPU 只需要关注栈顶,不用维护复杂的调用链;真正的执行顺序,在压栈的过程中就已经确定好了。
每次方法的调用都会产生一个栈帧,一个函数能拥有多个栈帧
来看一个简单的案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func c(n int) int { return n * 2 }
func b(n int) int { return c(n) + 1 }
func a(n int) int { return b(n) + 100 }
func main() { x := a(5) println(x) }
|
Text1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 时间 → main a b c │ ├─调用 a ──────► │ │ │ ├─调用 b ─────► │ │ │ │ │ ├─调用 c ──► │ │ │ │ │ │ │ ├ return 10 │ │ ├◄──────────┘ │ │ │ 算出 11 │ ├◄─────────────┘ │ │ 算出 111 ├◄─────────────┘ │ x=111 ▼
栈深度: 1 2 3 4 (最深) ←──── 增长 ────→ ←──── 收缩 ────→
|
Text1 2 3 4 5 6 7 8 9 10 11
| 低地址(栈顶) ┌─────────────────────┐ │ c 的栈帧 │ ← 最后调用 ├─────────────────────┤ │ b 的栈帧 │ ├─────────────────────┤ │ a 的栈帧 │ ├─────────────────────┤ │ main 的栈帧 │ ← 最先调用,栈底 └─────────────────────┘ 高地址(栈底)
|