通过Stack Overflow线程栈溢出的问题实例,详解C++程序线程栈溢出的诸多细节
目录
1、问题说明
2、从Visual Studio输出窗口中找到了线索,发生了Stack Overflow线程栈溢出的异常
3、发生Stack Overflow线程栈溢出的原因分析
4、线程占用的栈空间大小说明
5、引发线程栈溢出的常见原因和场景总结
6、在问题函数入口处添加return语句,在Debug下运行,还是会发生线程栈溢出异常
7、在问题函数入口处添加return,到release下运行就不报线程栈溢出的异常了
8、如何查看函数入口处分配栈内存的汇编代码?
9、最后
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战进阶(已更新到380多篇,持续更新中...)
https://blog.csdn.net/chenlycly/category_11931267.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)
https://blog.csdn.net/chenlycly/article/details/124272585Windows C++ 软件开发从入门到精通(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_12695902.htmlC++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_2276111.html 今天通过项目中遇到的一个线程栈溢出的实例,详细讲解线程栈溢出问题的排查过程以及涉及的诸多细节,以供大家借鉴或参考。通过Stack Overflow线程栈溢出的问题实例,详解C++程序线程栈溢出的诸多细节
1、问题说明
前段时间同事那边的代码出了问题,他为了实现某个新需求,在现有的代码中添加了一个业务消息,底层的业务模块将消息抛给上层模块,在上层模块中添加了该消息的响应函数去处理该消息,编译代码后运行,程序就直接闪退了。
同事排查分析了很久,始终找不到问题,于是找到我,让我帮忙分析一下。他说将新加的消息处理函数注释掉,就不会有崩溃,放开代码后,崩溃就是必现的。基本可以确定是新加的消息处理函数有问题,但同事一直没找到原因。
2、从Visual Studio输出窗口中找到了线索,发生了Stack Overflow线程栈溢出的异常
我到同事那边后,他复现了问题,打开函数调用堆栈页面,看程序崩溃在一个不相关的模块中。从现有的堆栈,看不到出问题的接口。切换到Output输出窗口中,看到了输出窗口的打印,看到了Stack overflow线程栈溢出的打印:
联想到是新加的消息处理函数后出现的,估计是新加的消息处理函数中引发了线程栈溢出的问题。
于是让同事打开新加的消息处理函数的代码:
一眼就看出来问题,用TInstantConference_Api结构体定义了一个局部变量:
TMtInstantConference_Api tInstanceConfInfo;
这个结构体我们以前也经常用,该结构体的成员很多,定义的很大很复杂。用该结构体定义的局部变量占用的就是栈空间,会占用很大的栈空间,导致所在线程占用的总的栈空间超过了分配给线程的栈空间的上限,引发了Stack overflow线程栈溢出的异常。
3、发生Stack Overflow线程栈溢出的原因分析
在程序中添加以下的测试代码:
int nSize = sizeof(TMtInstantConference_Api);
看看这个结构体的大小。打断点调试运行,上述代码返回的该结构体的大小为1096432/1024/1024 = 1.04MB。
在Windows系统中,系统给每个线程分配的默认栈内存大小是1MB,而此处直接使用这个TInstantConference_Api结构体直接定义一个局部变量,光这个局部变量占用的栈内存就达到了1.04MB,就超过了所在线程的1MB的栈内存的上限,所以产生了Stack overflow线程栈溢出的异常。
4、线程占用的栈空间大小说明
线程是系统分配栈空间的基本单元,即栈空间是分配给线程使用的。函数中的局部变量会占用栈内存,函数调用时传递给被调用函数的参数占用的内存空间也是栈内存。
在函数调用时,主调函数可能会通过栈将要传递给被调用函数的参数内存中的值压到栈上(栈内存),传递给被调用函数。这点在32位程序中比较常见,但在64位程序中因为64位寄存器比较多,就直接使用寄存器传递了,寄存器传递相对栈内存传递,效率会高一些。当然传递的参数比较多,或者参数内存比较大时,也会使用栈内存传递参数。
线程在某一时刻占用的栈空间的实际大小,是当前线程的函数调用堆栈中所有函数占用的栈空间之和,如果总和超过了系统分配给当前线程的栈空间上限,就会引发Stack Overflow线程栈溢出的异常,进而导致程序发生崩溃。
在Windows系统中,系统给线程分配的默认栈内存大小是1MB。在Linux系统中,系统给线程分配的默认栈内存大小是8MB,可以在Linux系统中使用ulimit命令查看:
这里涉及到C++程序在运行时所占用的内存分区,一般可分为栈内存区、堆内存区、全局/静态内存区、文字常量内存区及程序代码区5大分区。关于C++程序的内存分区,可以查看我的文章:
实例详解C++程序的五大内存分区https://blog.csdn.net/chenlycly/article/details/120958761
5、引发线程栈溢出的常见原因和场景总结
引发线程栈溢出问题可能有以下几个可能:
1)函数递归调用的深度过深
因为一直在递归调用,在到达最底下的那层调用之前,递归函数一直没返回,栈空间一直没有释放,导致当前线程占用的栈空间越来越多,达到上限。
2)消息上触发函数的死循环调用
消息触发的函数死循环调用,因为死循环调用了,函数的栈空间一直没释放,导致当前线程占用的栈空间越来越多。这个问题我们在实际项目中遇到过两次。
3)定义了一个占用内存很大的局部变量
比如定义了一个很庞大的结构体,在一个函数中用该结构体定义了一个局部变量,假设该结构体接近或者大于1MB,则会直接导致线程栈溢出。
4)函数中使用switch...case语句,包含了大量的case分支
每个case分支中都定义了局部变量,导致当前函数占用了大量的栈空间。case分支中的局部变量的生命周期是在case分支中的,即代码运行到对应的case分支中时该分支中的局部变量才有“生命”,但其实这个局部变量的栈空间已经在函数入口处分配好栈空间了,并不是代码执行到case子句中才分配栈空间的。这点可以通过编写测试代码,查看函数入口处给当前函数分配栈空间的汇编代码就能看出来了,可以先顶一个变量查看汇编代码看看分配了多少栈空间,然后再增加一个变量,看看分配的栈空间是否变大。
5)多个if-else分支,每个分支中都有定义局部变量
引发问题的原因与多个case语句的原因是类似的,此处就不再赘述了。
上述问题场景我在项目中都遇到过,我也是通过项目遇到的问题总结出上述场景的。实践出真知,大家要养成多思考多总结的习惯,这对提升个人技术水平、积累实践经验是很有用处的!
之前也排查过一个典型的线程栈内存溢出问题,感兴趣的话,可以去查看我之前写的文章:
线程栈溢出异常,程序崩溃在汇编代码test dword ptr [eax],eax上的问题排查https://blog.csdn.net/chenlycly/article/details/131743305
6、在问题函数入口处添加return语句,在Debug下运行,还是会发生线程栈溢出异常
既然问题出在新加的消息处理函数中,同事尝试直接在该函数的入口处添加一句return语句:
直接将当前消息处理函数return掉。然后再次测试,还是会出现闪退,依旧是Stack overflow线程栈溢出的异常。
其实依然出问题的原因很简单,可以查看该函数的汇编代码,在函数的入口处的汇编代码中我们可以看到给当前函数分配栈空间的汇编语句:(随便找个函数,在函数入口处设置断点,命中断点后,鼠标右键点击断点附近的代码,在弹出的右键菜单中点击“转到反汇编”去查看汇编代码上下文即可看到)
这句汇编代码在return之前,并且是编译时就确定加到二进制文件中的。
所以,即使在函数入口处加了一句return,但函数入口处的汇编还是分配了栈空间,即还没执行到return这句代码时就已经给当前函数分配了栈内存,所以还是会导致Stack overflow线程栈溢出的异常。因为还没执行到问题函数的内部,在校验当前线程栈空间的代码时就出现了线程栈溢出的异常,所以函数调用堆栈中看不到该函数的调用。
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:(该精品技术专栏的订阅量已达到480多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!)
C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:
C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795
常用的C++软件辅助分析工具有PE工具、Dependency Walker、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!
专栏3:(本专栏涵盖了多方面的内容,是当前重点打造的专栏,专栏文章已经更新到380多篇,持续更新中...)
C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、C++11及以上新特性(不仅看开源代码会用到,日常编码中也会用到部分新特性,面试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件异常的手段与方法、分析C++软件异常的基础知识、常用软件分析工具使用、实战问题分析案例等)、设计模式、网络基础知识与网络问题分析进阶内容等。
专栏4:
VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585
将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。
专栏5:
Windows C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根据多年C++软件开发实践,详细地总结了Windows C++ 应用软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
7、在问题函数入口处添加return,到release下运行就不报线程栈溢出的异常了
同事特意测试了一下,保留函数入口处的return语句:(也可以在工程设置中,在Debug下开启优化,当然一般不这么设置,此处是为了验证问题)
编译release版本,然后跑release版本并没有发生Stack overflow线程栈溢出的异常。也让我帮忙看看这是啥原因。
其实原因很简单,想想应该就知道了,是Release下的优化起的作用。因为Debug下为了调试将优化关闭了,而Release下是开启优化的,在Release下编译器发现函数入口处就return了,就直接把return后面的代码都优化掉了,只保留return语句,所以生成的二进制文件中就不会分配那么大的栈内存了,就不会有线程栈溢出的异常了。
8、如何查看函数入口处分配栈内存的汇编代码?
随便找一个函数,在函数开始处打上一个断点:
然后开启调试运行,当命中断点时,直接右键点击断点处,在弹出的右键菜单中点击“转到反汇编”菜单项:
即可查看对应的汇编代码,给函数分配栈空间的代码如下:
系统给线程分配的1MB的栈空间,当前线程中的函数会占用这一栈空间,这里涉及到ebp和esp两个寄存器,ebp用来存放当前函数的栈基址,esp用来存放当前函数的栈顶地址。
栈内存是从大地址向小地址使用的,对于被调用函数,其栈基址就是主调函数的栈顶地址,保存到ebp中,即mov ebp, esp(可以看上面汇编截图中的入口处)。然后在被调用函数中,在该被调用函数入口处的esp,就是主调函数的栈顶地址,减去一个数值,同时将减的结果赋值给当前的esp。这样被调函数占用的栈空间范围就是当前函数的esp到ebp中的范围,其中esp < ebp。
这里结合函数调用时的栈分布更好理解,关于函数调用时的栈分布,可以查看我的文章:
C++函数调用栈分布详解https://blog.csdn.net/chenlycly/article/details/121001096
9、最后
本文通过一个Stack Overflow线程栈溢出的问题实战分析实例,详细讲解了线程栈溢出涉及到的诸多细节,有一定的参考价值,希望对大家能有所帮助。
相关文章:

通过Stack Overflow线程栈溢出的问题实例,详解C++程序线程栈溢出的诸多细节
目录 1、问题说明 2、从Visual Studio输出窗口中找到了线索,发生了Stack Overflow线程栈溢出的异常 3、发生Stack Overflow线程栈溢出的原因分析 4、线程占用的栈空间大小说明 5、引发线程栈溢出的常见原因和场景总结 6、在问题函数入口处添加return语句&…...

LeetCode刷题笔记 | 3 | 无重复字符的最长子串 | 双指针 | 滑动窗口 | 2025兴业银行秋招笔试题 | 哈希集合
🙋大家好!我是毛毛张! 🌈个人首页: 神马都会亿点点的毛毛张 这是一道银行的面试题,就是简单?! LeetCode链接:3. 无重复字符的最长子串 1.题目描述 给定一个字符串 s ,…...
验证cuda和pytorch都按照成功了
要验证您的PyTorch是否能够调用CUDA,您可以执行以下步骤: 1. **检查CUDA是否可用**: 在Python中运行以下代码来检查CUDA是否可用: python import torch print(torch.cuda.is_available()) 如果输出为 True&…...
iOS开发如何自己捕获Crash
为了在iOS中捕获和处理未捕获的Objective-C异常和系统信号引起的崩溃,可以使用NSSetUncaughtExceptionHandler和标准的Unix信号处理机制来实现。这能帮助你记录绝大部分的崩溃信息。以下是详细的实现步骤和代码示例: 一、系统崩溃处理 通过NSSetUncaug…...
雪花算法(Snowflake Algorithm)
雪花算法(Snowflake Algorithm)是一种分布式唯一ID生成算法,主要用于生成全球唯一的ID,广泛应用于分布式系统中,例如在数据库中作为主键。这个算法最初由Twitter提出,并且被广泛使用在很多大规模系统中。有…...

〖任务1〗ROS2 jazzy Linux Mint 22 安装教程
前言: 本教程在Linux系统上使用。 目录 一、linux安装二、linux VPN安装三、linux anaconda安装(可选)四、linux ROS2 安装五、rosdep init/update 解决方法六、安装GUI 一、linux安装 移动硬盘安装linux:[LinuxToGo教程]把ubunt…...
图像增强:使用周围像素填充掩码区域
制作图像需要填充的掩码区域,对需要填充的位置的mask赋值非0,不需要填充赋值为0使用cv2.inpaint对图像掩码mask中非0元素位置的图像像素进行修复。从而实现使用周围像素填充掩码区域cv2.inpaint 是 OpenCV 库中的一个函数,用于图像修复(inpainting),即填充图像中的损坏区…...

给虚拟机Ubuntu扩展硬盘且不丢数据
1.Ubuntu关机状态下先扩展,如扩展20GB 2.进入ubuntu,切换root登录,必须是root全选,否则启动不了分区工具gparted 将新的20GB创建好后,选择ext4,primary; 3.永久挂载 我的主目录在/并挂载到/dev/sda1 从图…...
Oracle(41)如何使用PL/SQL批量处理数据?
在PL/SQL中,批量处理数据是一种高效的方法,可以在数据库中处理大量数据,而无需逐行操作。批量处理数据的关键技术包括: PL/SQL表(索引表):在内存中存储数据以进行批量操作。FORALL语句…...

JavaEE 第2节 线程安全知识铺垫1
目录 一、通过jconsole.exe查看线程状态的方法 二、Thread类的几种常见属性 三、线程状态 一、通过jconsole.exe查看线程状态的方法 通过jconsole查看线程状态非常实用的方式 只要你安装了jdk,大致按照这个目录就可以找到这个可执行程序: 然后双击这…...
LeetCode Hot100 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。 你可以认为每种硬币的数量是无限的。 示…...

微信小程序接口实现语音转文字
一、效果展示 我们有一个按钮,点击“开始录音”按钮,此时按钮变成“停止录音”并开始计时,点击停止录音后,界面上即可展示返回的文字 二、代码实现 完整代码实现见github 1.小程序端代码 // index.js const recorderManager…...
[Spark Streaming] 读取 Kafka 消息, 插入到 MySQL
以下是一个简单的使用 Spark Streaming 读取 Kafka 消息、统计数据后插入到 MySQL 中的 Scala 代码示例: import org.apache.spark.SparkConf import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.streaming.kafka.KafkaUtils…...

精选3款国内wordpress 主题,建站首选
WordPress作为一款功能强大且易于使用的建站平台,已经成为了许多企业和个人搭建网站的首选。为了帮助大家更好地选择适合自己的WordPress主题,小编将为大家推荐三款国内优秀的WordPress主题:子比主题、OneNav主题和RiTheme主题。 1.子比主题…...
JavaScript之 Uint8Array 类型数组(solana pda场景中的大小端)
文章目录 JavaScript之 Uint8Array 类型数组numberToUint8Array 数字转换为Uint8Array为什么要把数字转换为Uint8Array数字转换为Uint8Array的大小端问题solana pda场景中的大小端JavaScript之 Uint8Array 类型数组 Uint8Array 数组类型表示一个8位无符号整型数组,创建时内容…...

《Windows API每日一练》24.1 WinSock简介
本节将逐一介绍WinSock的主要特性和组件,套接字、WinSock动态库的使用。 本节必须掌握的知识点: Windows Socket接口简介 Windows Socket接口的使用 第178练:网络时间校验 24.1.1 Windows Socket接口简介 ■以下是WinSock的主要特性和组件…...
openwrt编译Dockerfile
一、Dockerfile FROM ubuntu:20.04ENV TZAsia/ShanghaiRUN apt-get update && \apt-get install -y --no-install-recommends tzdata && \ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && \dpkg-reconfigure --frontend noninteractive tzdata &am…...

【C语言】分支与循环(循环篇)——结尾猜数字游戏实现
前言 C语言是一种结构化的计算机语言,这里指的通常是顺序结构、选择结构、循环结构,掌握这三种结构之后我们就可以解决大多数问题。 分支结构可以使用if、switch来实现,而循环可以使用for、while、do while来实现。 1. while循环 C语言中…...

【数据结构】链表篇
文章目录 1.链表的概念以及结构2.链表的分类2.1 单向或者双向2.2 带头或者不带头2.3 循环或者不循环2.4 无头单向非循环链表和带头双向循环链表 3.单链表的实现3.1 准备工作3.2 节点的创建3.3 单链表的释放3.4 打印链表3.5 单链表的尾插3.6 单链表的尾删3.7 单链表头删3.8 单链…...
Python SciPy介绍
在数据科学和工程领域,Python已经成为了一个不可或缺的工具,这主要得益于其强大的库和框架支持。其中,SciPy库作为Python科学计算的核心库之一,为研究人员、工程师和数据分析师提供了大量高效的算法和数学工具。本文将带您深入了解…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...