【C语言的栈溢出问题以及部分解决】

这篇具有很好参考价值的文章主要介绍了【C语言的栈溢出问题以及部分解决】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C语言的栈溢出问题

例如:针对学习过程中遇到的栈溢出问题



前言

溢出,常见的解释是:程序外部的数据大小,超出其规定数据类型所能表达的范围,从而造成正常程序预期外的错误表达。 溢出一般被当做黑客攻击操作系统的途径。具体的溢出类型有以下三种:缓冲区溢出(Buffer overflow);内存溢出(memory overflow);数据溢出(data overflow)。本文只谈及缓冲区溢出的——栈溢出


栈溢出(Stack overflow)

在谈栈溢出之前,先提一下一个程序所占用计算机内存的分布:
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

导致栈溢出的原因

一般导致栈溢出的原因有五点原因:
①函数递归层次太深。 递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈(栈)溢出。
②局部变量体积太大。 (因为局部变量是存储在栈中的)
③动态申请空间使用之后没有释放。 虽然程序中动态申请空间没有释放,在程序结束之后,系统也会将进程中申请的内存全部释放,但是程序运行中时间过长,导致内存占用过大,进程耗尽所有内存。申请后不手动释放就会内存泄露(C语言中是用malloc和free来分配空间和释放空间(内存)的)
④数组访问越界。 C语言中是没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,就会出现内存访问错误 (例如进行字符串拷贝或处理用户输入等)
⑤指针非法访问。 指针保存了一个非法地址(其实也可以看做是指针越界),再通过指针访问所指向地址的时候就会出现内存错误。
此处我想提及学习过程中遇到的递归循环发生的系统栈溢出。

①函数递归层次太深

①函数递归层次太深

为了避开数据溢出的干扰,这里我选用了一个比较简单的加法运算来举例

int Fun(int n)
{
	if (n == 1)
		return 1;
	else
		return n + Fun(n - 1);
}
int main()
{
	printf("%d\n", Fun(10000));
	return 0;
}

在运行到第5211次(当然每一次都不确定,大约都是在5000次左右)的时候,直接就爆出Stack overflow(栈溢出)
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++
那么这段程序在栈区干了些啥呢?就把人家弄爆了。(入栈,此程序还未出栈,以下只是补充画的出栈图)
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

递归层次太深,其实也是函数调用过多。函数调用的本质是:入栈(push)和出栈(pop),递归中每一次调用函数时,都会入栈保存一个栈帧,里面保存着该函数的调用信息、内部变量(后面简称这两玩意为“调用记录”),这样栈空间总有被耗干的时候。 此处的Fun函数就是如此,”被调用10000次,一直调用到5211次时还没到可以返回的时候,我们写的这个程序还没到出栈,栈空间就满了。并且我们发现,即使是栈没满的情况,程序正常运行,函数又往反方向一个一个的出栈,其实效率是很慢的。

1.修改栈区空间大小

解决问题①(函数递归层次太深)的方法还是挺多的,直接一点的就修改栈堆保留大小 ,它的默认是1MB,改大了就可以了。(此处针对VS修改)
具体步骤:项目->属性->链接器->堆栈保留大小栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

现在就不会溢出了。
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

2.尾部递归优化

然后再就是尾部递归优化递归:函数自己调用自己;尾调用:函数最后一步是调用另一个函数。那么尾递归就是函数最后一步调用自己(两个结合起来)。 那么就永远只有一个栈帧,调用函数会在栈区产生栈帧,最后一步return …把当前函数的结果当作参数传递给了另外一个自身调用,由于尾调用是函数的最后一步操作,所以它不需要外层函数的调用记录了,这样外层函数的栈帧空间就被释放了(因为它已经完成了return任务,已经没用了)。
之前的普通递归之所以不会释放,是它每次还需要保留一个外部变量n,因为执行Fun(n-1)调用Fun()时还要用到n。而尾部递归本来返回的就是此次调用的结果(作为下一次调用的函数参数),也没有额外的变量需要被保存等着算,那么上一层的Fun_iter()栈帧空间释放了也不影响return的下一次调用。

int Fun_iter(int num, int sum)
{
	if (num == 1)
		return sum;
	else
		return Fun_iter(num - 1, num + sum);
} 
int main()
{
	printf("%d\n", Fun_iter(10000, 1));
	return 0;
}

上面讲的是尾部递归,那么传值的时候,谁会去传两个值,用户输入秉持的是用户的体验,用户说要算10000以内数字的和,他肯定只会输入10000啊,不会帮你再传一个参数1的。所以可以改良一下:

int Fun(int n)
{
	return Fun_iter(n, 1);
}
int Fun_iter(int num, int sum)
{
	if (num == 1)
		return sum;
	else
		return Fun_iter(num - 1, num + sum);
} 
int main()
{
	printf("%d\n", Fun(10000));
	return 0;
}

现在栈也不会溢出了,因为尾递归永远只保留一次调用记录。
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++
当然我想提醒一下,尾部递归优化的实际效果是来源于编译器的优化!!!意思就是是编译器决定要不要按照你的逻辑,在尾部递归的时候每次释放上一次空间,这是由编译器的心情决定的。如果你的编译器不支持,那么即使使用尾部递归也会栈溢出。对于gcc编译器3.2.2(别的博主的旧版本)-O2或者-O3就可以完成尾部递归优化。VS目前看到的是从2010版本使用O1及以上的优化选项,一般就会尾递归优化。我是VS2019,用的O1就可以尾部递归优化了。

(附一)设置优化选项(O1/O2)

右键你的项目文件->属性->C/C+±>优化->优化->最大优化(优选大小)(/O1)
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

这里优化默认的是已禁用(/Od),改成O1就可以了

栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

(附二)解决“/O1”和“/RTC1”命令行选项不兼容

当然如果你出现以下报错
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

解决:项目->属性->代码生成->基本运行时检查->默认值

栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++
将原本的两者(/RTC1,等同于 /RTCsu) (/RTC1)改为默认值
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++
现在你的vs编译器可以尾部递归优化了。
对于gcc编译器,想改就加个 #pragma GCC optimize(2) ,就可以改成-O2
我这个是在vscode下配置的gcc,我的gcc是 8.1.0版本的,没有更改优化选项,也可以实现尾部递归优化。栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

②局部变量体积太大

②局部变量体积太大。

函数里定义的很大的局部变量,(比如很大的数组)也会导致栈溢出,因为局部变量是存在栈区的。这种情况,你要么直接修改栈区大小,上面已讲。要么你就把局部变量改为全局变量(静态变量),存在静态区或者存在堆上。
比如举个例子:

int main()
{
	int a[1000000] = {0};
	return 0;
}

在没有修改栈区空间的情况下会栈溢出,如图:
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

解决问题

那么我们可以把它设为全局变量,或者在堆区分配 动态内存分配空间

栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

记得malloc与free要一起搭配使用哦!malloc()就是在堆区分配一块指定大小的内存空间,用来存放数据。

栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++

③动态申请空间使用之后没有释放

③动态申请空间使用之后没有释放。

堆一般由程序员手动释放,如果不释放,程序结束可能有OS收回,也有可能不会,全凭OS心情。比如malloc()申请完了,没释放,就噶了。②中写了就不写了。
解决问题:没释放那就去释放呗!

④数组访问越界

④数组访问越界。

先来看看数组越界访问后,发生些什么吧。发现访问下标<0的元素,会发生下限越界 ;访问下标>数组长度,会发生上限越界。(此处注意此时访问越界,没有报错,只有警告)还没完呢,咱继续。
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++
之前介绍说得也很清楚了,数组访问越界后,栈区还是会给越界部分分配空间,越界后会按照数组的内存继续写(你看地址是连续的),如果此时数组越界部分的内存有数据,那么越界部分数据会“被”它覆盖掉。 所以你想如果你想要存入的数据很重要,数组越界后,又碰巧遇到越界那部分内存原本放有别的数据,那么你要存入的数据就被改写了(被覆盖),那问题就严重咯~
我们来验证一下,是不是会被覆盖。

int main()
{
	int i = 0;
	int arr[] = { 1,2,3,4,5 };
	arr[6] = 8;
	//printf("arr[%d] = %-10d %p\n", 6, arr[6], &arr[6]);
	for (i = 0; i <= 7; i++)
	{
		printf("arr[%d] = %-10d", i, arr[i]);
		printf("%p, i的地址%p\n", &arr[i], &i);
	}
	arr[5] = 6;
	//printf("arr[%d] = %-10d %p", 5, arr[5], &arr[5]);
	return 0;
}

栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++
可以看到,经过越界访问得到的随机值,已经被刚开始我们写的arr[6]给覆盖了。并且通过arr[5]的例子,我们发现即便arr[5]已存有了随机值-858993460,我们也可以对越界访问到的内存进行改写,和正常修改数组的值一样。 不过会报:Stack around the variable ‘arr’ was corrupted 的错,这个error的意思是变量arr周围的栈被破坏了。arr只开辟了00FDFE24~00FDFE34中的这段内存,但是我们修改的是这段空间之外的内存进行修改,这就是这个error的意思。之前还看到有的博主说数组下限越界不会报错,无法察觉,辟个谣,会报错哈!无论上限下限越界都是arr周围栈被破坏。
这里还有个点就是arr[7] = 7,以及arr[7]的地址和 i 的地址是一样的。
栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++
也就是说本来40里面保存的是变量 i 的值,而访问中 i 是下标,一直在做++运算,到arr[7]时,40里保存的是 i = 7。而当访问到arr[7]时,编译器发现这块黄色的内存里已经放有数据7了,就直接覆盖掉了arr[7]越界访问会产生的随机值。所以arr[7]得到的是7。
想解决这类问题,那就只能靠程序员自己注意了。因为本来出现数组访问越界的问题,其根本还是程序员的自己的问题。

⑤指针非法访问

⑤指针非法访问。

此时指针已经不叫指针了,它叫野指针,野指针:指针指向的位置不可知,或者是非法的,无效的。

int main()
{
	int arr[] = { 1,2,3,4,5 };
	int* pa = &arr;
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		//printf("arr[%d]的值%-12d ", i, arr[i]);
		*pa = i;
		//printf("Pa访问arr[%d]的地址%p  ",i, &*pa);
		//printf("arr[%d]的值%-12d\n", i, arr[i]);
		pa++;
	}
	return 0;
}

栈溢出的原因及解决办法,c语言,开发语言,数据结构,算法,c++
看到这个error是不是很熟悉,刚刚才遇到过,仔细看代码,在for循环中,咱除了访问了合理合法的arr中的5个元素,4个下标,从下标值为5开始,就是在非法访问了(越界访问),得到的值就如同数组越界访问所得的随机值,接着我觉得还不够,还干了一件把访问到的值改写*pa = i,改写数组范围内的没有任何问题,但是超出数组范围,也就是从5开始,你又开始自己开创空间,还往里面改写数据(赋值覆盖),也就是变量arr周围的栈被破坏了。


总结

以上就是今天记录的内容,本文介绍了五种导致栈溢出的问题以及解决办法,原计划是还准备了整型溢出和算术溢出的问题,那就安排到其他的子文章中记录吧。(求生欲:小白专属)文章来源地址https://www.toymoban.com/news/detail-810032.html

到了这里,关于【C语言的栈溢出问题以及部分解决】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • VUE项目运行失败原因以及解决办法

    Ctl +J 打开终端,并运行如下命令: npm run serve 正常情况下,就可以得到本地和网络链接,如下:  点击链接即可进入到编辑好的页面。 不过,你也可能遇到如下情况↓↓↓ ENOENT: no such file or directory, open \\\'D:codehuman_resource_management 3package.json\\\' 这个错误表明npm无法找到指定的

    2024年02月04日
    浏览(38)
  • 电脑磁盘数据错误(循环冗余检查)的原因以及解决办法

    造成的原因 出现这种情况,是因为你的这个文件有某些数据记录不正确,也有可能硬盘某处物理损坏读不过去(也就是硬盘有坏道)。通常情况下造成的原因有长时间不关机,软件没退出强制性关机,磁盘检查和优化时强制性退出所导致的。 解决办法 如果是机械硬盘的通道

    2024年02月12日
    浏览(136)
  • Redis中的缓存雪崩、击穿、穿透的原因以及解决办法

    缓存雪崩、击穿、穿透一旦发生,会导致大量的请求积压到数据库层。如果请求的并发量很大,就会导致数据库宕机或是故障,这就是很严重的生产事故了。 俗话说,知己知彼,百战不殆。了解了问题的成因,我们就能够在应用Redis缓存时,进行合理的缓存设置,以及相应的

    2024年02月12日
    浏览(50)
  • HBase中master正常启动,自动关闭——原因以及解决办法

    regionserver一直在运行,而master启动之后,过一会自动停止,因为master停止,所以也无法访问hbase管理web页面。 可能的原因: 虚拟机的时间不同步 防火墙没有关闭 hbase中的hbase-site.xml文件中的属性值(hbase.rootdir)主机端口不一致 缺少配置 虚拟机时间不同步,虚拟机集群时间不

    2024年02月06日
    浏览(47)
  • Linux报 “illegal instruction” 异常的原因以及解决办法

    当 Linux 上的程序收到 “Illegal instruction” 错误时,它表示正在尝试执行一种不支持的 CPU 指令,通常是由于使用了错误的 CPU 架构导致的。主要有以下几个原因: 1、CPU 架构不匹配:程序被编译为针对一个不匹配的 CPU 架构而运行。 2、编译器问题:程序被使用了不兼容的编译

    2024年04月10日
    浏览(41)
  • VUE项目运行失败原因以及解决办法(以vscode为例)

    Ctl +J 打开终端,并运行如下命令: npm run serve 正常情况下,就可以得到本地和网络链接,如下:  点击链接即可进入到编辑好的页面。 不过,你也可能遇到如下情况↓↓↓ ENOENT: no such file or directory, open \\\'D:codehuman_resource_management 3package.json\\\' 这个错误表明npm无法找到指定的

    2024年02月04日
    浏览(51)
  • 金浪路由器上不了网的原因以及解决办法

        一、首先检查是否存在连接故障,进入与路由器相连的电脑的设备管理器,检查是否已经正确安装了电脑网卡,如果发现有故障,尝试重新安装网卡,主要是查看是否是网卡驱动装错了,这种情况不多,但也不容易被发现,可以从官网上下载对应的驱动重新装一遍,以确

    2024年02月05日
    浏览(50)
  • Win7无线网络无法连接的原因以及解决办法

    有网友给小编反应大家无线网络无法连接导致联网连不上去,这种情况很多是网友的设置出了问题,那是一些小白朋友不会将无线网络的端口给关闭掉了原因,那么小编就来讲解 无线网络无法连接的解决方法 吧。就以win7系统为例吧。 具体操作方法如下: 1、首先要查看无线

    2024年02月05日
    浏览(65)
  • vue/react项目刷新页面出现404的原因以及解决办法

    问题描述:vue/react项目,正常的页面操作跳转,不会出现404的问题,但是一旦刷新,就会出现404报错。 产生原因:我们打开vue/react打包后生成的dist文件夹,可以看到只有一个 index.html 文件及一些静态资源,这个是因为vue/react是单页应用(SPA),只有一个index.html作为入口文件,

    2024年02月06日
    浏览(50)
  • 服务器报错nginx 502 Bad Gateway的原因以及解决办法

    网站页面出现502badgateway怎么办?今天我们来分析一下原因和解决办法。 nginx出现502多数是属于后端的问题,后期就是PHP的问题,在php服务当中,有两个参数非常的重要:max_requestst 和max_children;具体的原因必须要查看日志才可以弄明白! 1:FastCGI进程是否已经启动 ps aux|grep

    2023年04月20日
    浏览(67)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包