Effective C++笔记之二十四:stack overflow

news/2024/10/11 5:25:37/

溢出(Stack Overflow)是指程序运行过程中,栈空间被耗尽,导致无法继续分配栈内存的错误。C++程序中,栈用于存储函数调用的局部变量、返回地址、函数参数等。当栈空间耗尽时,会引发栈溢出,通常导致程序崩溃。
如果用的是Visual Studio,报错弹窗如下图所示。

如果用的是其他IDE,比如所Qt Creator,直接crash了,无任何提示。
通常的现象就是调用了一个函数,然后就崩了,注释调这个函数,一切正常。
函数本身当然是没问题的,比如:

ui->lable->setText("Hello World");

经调查发现是因为有函数中定义了大数组,大数组会占用大量栈空间,而函数调用也要使用栈,当刚好达到栈空间的临界值时就stack overflow了。

一.栈溢出的常见原因

1. 递归调用层次过深

递归函数在每次调用时,都会在栈上分配一个新的栈帧。当递归调用层次过深且没有足够的栈空间时,栈会溢出。
例子:

int main()
{main();return 0;
}

原因:
每次递归调用会占用栈空间,当递归调用层次过多时,栈内存耗尽,导致溢出。
在C++中,函数压栈(函数调用)和出栈(函数返回)是函数调用过程中的两个关键步骤。
函数压栈(函数调用)的过程如下:
①调用指令:在函数调用点,会发出一个调用指令(如call指令),将控制权转移到被调用函数的入口点。
②保存返回地址:调用指令执行前,当前函数的返回地址(下一条指令的地址)会被压入栈中,以便在函数执行完毕后返回到正确的位置。
③参数压栈:函数调用时,将函数的参数按照一定的顺序压入栈中。通常,参数从右至左依次入栈。
④保存寄存器值:在一些体系结构中,函数调用时需要保存一些寄存器的值,以便在函数执行完毕后能够恢复原始的寄存器状态。
⑤帧指针与局部变量压栈:为了支持函数内的局部变量和堆栈的动态分配,通常会在栈上维护一个帧指针(frame pointer),它指向当前函数的栈帧(stack frame)的底部。同时,函数内部定义的局部变量也会在栈上分配空间。
⑥执行函数体:一旦函数的参数、局部变量和其他上下文信息都被压入栈中,函数体中的代码开始执行。
函数出栈(函数返回)的过程如下:
①恢复寄存器值:在一些体系结构中,函数返回时需要恢复之前保存的寄存器的值。
②释放局部变量和帧指针:函数返回后,会释放函数内部定义的局部变量所占用的栈空间,并将帧指针恢复到上一层函数的栈帧。
③弹出参数和返回地址:函数返回后,参数和返回地址会从栈中弹出,将控制权返回到调用函数的正确位置。
解决办法:
●优化递归:尽量减少递归调用的深度。
●使用迭代:将递归算法改写为迭代算法,这样可以避免递归带来的栈空间占用。
●尾递归优化(如果编译器支持):尾递归可以让编译器在递归调用时重用当前的栈帧,减少栈空间的消耗。

2. 局部变量占用过多的栈空间

局部变量存储在栈上,特别是大型数组或对象。如果局部变量过大,可能会导致栈空间耗尽。
例子:

int main()
{char myArray[1024 * 1024 * 2];return 0;
}

PS:需在Debug模式下测试,因为Release下未使用的局部变量会被优化掉。
原因:
栈空间有限(window上通常为1~2MB,linux下也只有几MB),而大数组会占用大量栈空间,导致栈溢出
解决办法:
使用堆代替栈,将大数组或对象放到堆上

二.window上默认栈空间

首先要明确的是栈空间的大小是编译器在编译时指定的,每个线程都有自己独立的栈。

1.VC++编译器

For ARM64, x86, and x64 machines, the default stack size is 1 MB.

参考链接:/STACK (Stack allocations)
CreateThread函数的API文档中也有类似说明

By default, every thread has one megabyte of stack space. Therefore, you cannot create 2,048 or more threads on a 32-bit system without /3GB boot.ini option. If you reduce the default stack size, you can create more threads. 

参考链接:CreateThread function
PS:CreateThread有个参数dwStackSize可以设置线程栈的大小
关于Windows进程和线程数的上限,可以参考这篇文章:Pushing the Limits of Windows: Processes and Threads
Windows进程创建时,会分配虚拟内存空间,一般而言,32位操作系统为4G(2G用户空间+2G内核空间),而64位操作系统,虚拟内存寻址则大的多(8 TB用户空间+8TB内核空间)。WIndows线程创建时,会分配用户态Stack(栈)来传递函数参数(function parameters), 管理本地变量(local variables), 保存函数返回地址(function return addresses). 一般而言,线程用户态的stack默认保留大小为1 MB。所以在32位操作系统,线程数理论上也就是 2048个,一般会小于这个数。
用Visual Studio 2017 写个程序测试一下栈大小

#include <iostream>int main()
{static int count = 0;char myArray[1024 * 10];std::cout << ++count << std::endl;main();return 0;
}


可以看到,算上main()函数的递归,栈空间是接近1MB的

2.MinGW编译器

代码一样,结果如下:
也就是说,此时的栈空间是接近2MB的
默认栈空间大小是可以设置,但一般没必要去改它,有兴趣的同学可以自行百度

参考链接:https://zhuanlan.zhihu.com/p/642851340?utm_id=0

原文链接:Effective C++笔记之二十四:stack overflow-CSDN博客 


http://www.ppmy.cn/news/1537282.html

相关文章

python 实现dijkstra迪杰斯特拉算法

dijkstra迪杰斯特拉算法介绍 Dijkstra&#xff08;迪杰斯特拉&#xff09;算法是由荷兰计算机科学家狄克斯特拉于1959年提出的&#xff0c;它是一种用于计算图中一个节点到其他所有节点的最短路径的算法。该算法主要用于解决有权图&#xff08;即图中的边有权值&#xff09;中…

JVM有哪些参数以及如何使用

JVM&#xff08;Java虚拟机&#xff09;参数用于调整和优化Java应用程序的性能和行为。这些参数主要分为标准参数、非标准参数&#xff08;以-X开头&#xff09;和高级参数&#xff08;以-XX开头&#xff09;。以下是一些常见的JVM参数及其使用方法&#xff1a; 标准参数 -se…

基于单片机的山林远程环境监测仪设计

本设计基于单片机的智能化的远程山林环境检测仪&#xff0c;该检测仪由硬件系统和软件系统构成。电源管理模块给整个硬件系统提供工作所需电源&#xff0c;系统可完成山林环境有关的温度、湿度、火焰和海拔高度的采集&#xff0c;并且可通过与按键设置阈值作对比判断危险情况&a…

数学基础 -- 微积分之链式求导法则

链式求导法则 链式求导法则&#xff08;Chain Rule&#xff09;是微积分中非常重要的法则&#xff0c;用于计算复合函数的导数。其基本思想是&#xff1a;如果一个变量依赖于另一个变量&#xff0c;而这个中间变量又依赖于另一个变量&#xff0c;那么可以通过链式法则把这些依…

第 17 场小白入门赛蓝桥杯

第 17 场小白入门赛 2 北伐军费 发现每次选大的更优&#xff0c;所以可以排序之后&#xff0c;先手取右边&#xff0c;后手取左边。 实际发现&#xff0c;对于 A − B A-B A−B 的结果来说&#xff0c;后手对于这个式子的贡献是 − − a i --a_i −−ai​ &#xff0c;也就…

使用 Go 和 Gin 框架构建简单的用户和物品管理 Web 服务

使用 Go 和 Gin 框架构建简单的用户和物品管理 Web 服务 在本项目中&#xff0c;我们使用 Go 语言和 Gin 框架构建了一个简单的 Web 服务&#xff0c;能够管理用户和物品的信息。该服务实现了两个主要接口&#xff1a;根据用户 ID 获取用户名称&#xff0c;以及根据物品 ID 获…

多jdk版本环境下,jenkins系统设置需指定JAVA_HOME环境变量

一、背景 由于不同项目对jdk版本的要求不同&#xff0c;有些是要求jdk11&#xff0c;有些只需要jdk8即可。 而linux机器上安装jdk的方式又多种多样&#xff0c;最后导致jenkins打包到底使用的是哪个jdk&#xff0c;比较混乱。 1、java在哪 > whereis java java: /usr/bin/…

【自注意力与Transformer架构在自然语言处理中的演变与应用】

背景介绍 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;序列到序列&#xff08;seq2seq&#xff09;模型和Transformer架构的出现&#xff0c;极大地推动了机器翻译、文本生成和其他语言任务的进展。传统的seq2seq模型通常依赖于循环神经网络&#xff08;RNN&…