关于函数递归调用导致的StackOverflow那点事

本文涉及的产品
资源编排,不限时长
无影云电脑企业版,4核8GB 120小时 1个月
无影云电脑个人版,1个月黄金款+200核时
简介: 关于函数递归调用导致的StackOverflow那点事

 身为工程师,Stack Overflow这网站大家应该都是熟到不能再熟。如果没有他帮忙解决各种莫名其妙的错误或者bug,可能连设定个开发环境都要搞半天,更不用说要开发了,产出直接降低好几倍,不如直接下班算了。

image.gif编辑

但今天要谈的不是那个Stack Overflow,而是要讲程序在使用內存时,因为调用栈的堆太高了不小心把內存用完,所产生的overflow。

认真写个stack overflow

先来看看以下这个用C写成的sum(n),他做的事情很简单,就只是用递回来计算从1到n的总和,譬如说sum(3)= 1 + 2 + 3 = 6。

int Sum(int n) {
    if (n == 1) {
        return 1;
    }
    return Sum(n - 1) + n;
}

image.gif

当然这写法很烂,你忍不住要吐槽,回到正题我只想让大家看看实际运行Sum(10)跟Sum(10000)也都有得到正确的结果。

但跑Sum(2000000)时就不是这样了,因为程序写太烂加上20000这数字太大,直接就原地炸开喷出Segmentation fault (core dumped),这就是所谓的Stack Overflow。

image.gif编辑

不出你的意料,他崩溃了,那么我们来讲解一下他的成因,首先看看到底什么是Stack。每个Process在启动时,系统都会分配一块內存给他使用,以最常见的内存布局来说,进程内的内存布局大概会长这样,由低位到高位主要分成Text、Data、Heap、Stack几个内存块。

最下面的Text内存块放的是要给CPU跑的可执行指令,一般来说你的代码越多,这部分就会越肥;而Data内存块放的就是进程内的全局、静态变量。

image.gif编辑

Text跟Data这两个内存块基本上是固定大小,因为你写的程序一旦编译好,全局变量的数量就会固定下来,不太会动态增长。但Heap跟Stack就不一样了,当程序有用到动态內存配置像是C语言的malloc/free那些空间就会被分配在Heap里面。

而最上面的Stack也就是这篇的重点,则是放函数调用时会用到的一些东西,像是传进来的参数、局部变量等等,每个函数被调用的时候会有一个stack frame被创造出来,结束时再马上释放掉。

深层递归发生了什么事?

那递归是怎么让栈爆掉的呢?以我们的例子来说,当我们调用Sum(3)时,他内部会先调用Sum(2),而Sum(2)又再调用Sum(1),因此整个栈里面会有多个栈帧依序叠起来,只有当最上层的Sum(1)执行结束了之后,才有办法继续执行下面的Sum(2)跟Sum(3)。

image.gif编辑

虽然栈可以动态增长,但他的大小也是有极限的,操作系统总不可能为了跑你的程序而把系统的内存都给你。

因此当你调用Sum(2000000)导致递归太深时,等待执行的函数又太多,但栈空间已经装不下更多帧,就会产生overflow的错误。

image.gif编辑

虽然一般来说都是递归没写好才会导致Stack Overflow,但其实Stack Overflow并不一定是递归造成的,只要你的call stack够深(真的要非常非常深),那就可能会有这个。

如何修改栈溢出

若真的不幸遇到Stack Overflow要怎么解决呢?第一步就是先检查递归的终止条件。像这边如果没有在n == 1时return 1,那就算只是跑个Sum(3)也会炸掉。

int Sum(int n) {
    int s = 0
    for (i = 1; i <= n; i++) {
        s += i;
    }
    return s;
}

image.gif

关于C,C++的内存布局

本片不在赘述,请看我之前的文章:

C++:28 --- C++内存布局(上)

C++:29 --- C++内存布局(下)

C++:30 ---C++类成员,成员函数的内存布局

相关文章
C4.
|
8月前
|
C语言
C语言函数的递归调用
C语言函数的递归调用
C4.
124 0
|
4月前
利用递归函数调用
利用递归函数调用。
39 9
|
7月前
|
C语言
C语言--函数递归与迭代
C语言--函数递归与迭代
|
8月前
|
算法 C语言
C语言函数递归调用详解与实战应用
C语言函数递归调用详解与实战应用
102 0
|
8月前
|
算法 C语言
C语言中的递归调用与递归函数
C语言中的递归调用与递归函数
131 0
|
8月前
|
C语言
C语言函数嵌套与递归调用的深入解析
C语言函数嵌套与递归调用的深入解析
110 0
|
C语言
C语言进阶教程(传值调用和传址调用的区别)
C语言进阶教程(传值调用和传址调用的区别)
129 0
|
8月前
|
算法 Java C语言
C语言函数的递归
C语言函数的递归
64 0
|
8月前
|
C语言
【C语言】指针进阶之传值调用与传址调用
【C语言】指针进阶之传值调用与传址调用
|
算法 C语言
C语言函数递归练习详解
C语言函数递归练习详解