函数运行时在内存中是什么样子?( 三 )


局部变量
我们知道在函数内部定义的变量被称为局部变量,这些变量在函数运行时被放在了哪里呢?原来,这些变量同样可以放在寄存器中,但是当局部变量的数量超过寄存器的时候这些变量就必须放到栈帧中了 。因此,我们的栈帧内容又一步丰富了 。

函数运行时在内存中是什么样子?

文章插图
 
?
 
细心的同学可能会有这样的疑问,我们知道寄存器是共享资源可以被所有函数使用,既然可以将函数A的局部变量写入寄存器,那么当函数A调用函数B时,函数B的局部变量也可以写到寄存器,这样的话当函数B执行完毕回到函数A时寄存器的值已经被函数B修改过了,这样会有问题吧 。这样的确会有问题,因此我们在向寄存器中写入局部变量之前,一定要先将寄存器中开始的值保存起来,当寄存器使用完毕后再恢复原值就可以了 。那么我们要将寄存器中的原始值保存在哪里呢?有的同学可能已经猜到了,没错,依然是函数的栈帧中 。
函数运行时在内存中是什么样子?

文章插图
 
?
 
最终,我们的小盒子就变成了如图所示的样子,当寄存器使用完毕后根据栈帧中保存的初始值恢复其内容就可以了 。现在你应该知道函数在运行时到底是什么样子了吧,以上就是问题3的答案 。
Big Picture
需要再次强调的一点就是,上述讨论的栈帧就位于我们常说的栈区 。栈区,属于进程地址空间的一部分,如图所示,我们将栈区放大就是图左边的样子 。
函数运行时在内存中是什么样子?

文章插图
 
最后,让我们回到文章开始的这段简单代码:
void func(int a) {if (a > 100000000) return;int arr[100] = {0};func(a + 1);}void main(){func(0);}想一想这段代码会有什么问题?原来,栈区是有大小限制的,当超过限制后就会出现著名的栈溢出问题,显然上述代码会导致这一问题的出现 。因此:
  1. 不要创建过大的局部变量
  2. 函数栈帧,也就是调用层次不能太多
 
总结
本章我们从几个看似没什么关联的问题出发,详细讲解了函数运行时栈是怎么一回事,为什么我们不能创建过多的局部变量 。细心的同学会发现第2个问题我们没有解答,这个问题的讲解放到下一篇,也就是协程中讲解 。




推荐阅读