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

本文涉及的产品
云服务器 ECS,每月免费额度200元 3个月
云服务器ECS,u1 2核4GB 1个月
简介: 关于函数递归调用导致的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.
|
2月前
|
C语言
C语言函数的递归调用
C语言函数的递归调用
C4.
14 0
|
5月前
什么是递归函数?怎样实现递归?
什么是递归函数?怎样实现递归?
|
5月前
|
算法 C语言
你会使用函数的递归和迭代吗?----------C语言函数学习(4)详解
你会使用函数的递归和迭代吗?----------C语言函数学习(4)详解
79 1
|
2月前
|
算法 Serverless Python
函数的递归调用
在编程中,递归是一种非常强大的技术,它允许函数直接或间接地调用自身。递归调用使得某些问题的解决变得简单而优雅,尤其是那些具有自然分治结构的问题。本文将介绍函数的递归调用概念,并通过示例代码展示其应用。
15 1
|
2月前
|
算法 Java C语言
C语言函数的递归
C语言函数的递归
9 0
|
8月前
|
算法 C语言
C语言函数递归练习详解
C语言函数递归练习详解
|
11月前
|
算法 程序员 编译器
C语言函数与递归
C语言函数与递归
91 0
|
12月前
|
Python
如何写出你的第一个递归函数?
如何写出你的第一个递归函数?
56 0
|
存储 JavaScript
为什么我要说:柯里化 == 闭包+递归?
柯里化是 JS 高程中不可或缺的重心,本篇带你来冲一冲它!!
|
算法
【学习笔记之我要C】函数递归
【学习笔记之我要C】函数递归
42 0