通过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科学计算的核心库之一,为研究人员、工程师和数据分析师提供了大量高效的算法和数学工具。本文将带您深入了解…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...