使用 STL 容器发生异常的常见原因分析与总结

目录

1、概述

2、使用STL列表中的元素越界

3、遍历STL列表删除元素时对迭代器自加处理有问题引发越界

4、更隐蔽的遍历STL列表删除元素时引发越界的场景

5、多线程同时操作STL列表时没有加锁导致冲突

6、对包含STL列表对象的结构体进行memset操作导致STL列表对象内存出异常

7、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html开源组件及数据库技术(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_2276111.html       有了STL容器之后,我们基本不再需要再去设计链表等数据结构了,STL中的vector、list、map等容器基本就能满足我们的需求了。但我们在使用STL列表可能会因为编码不规范遇到这样那样的问题,导致软件出现异常,特别是新人会因为缺乏经验容易犯一些错误。正好前段时间在技术群中有粉丝就遇到使用STL容器的问题,于是问我可能是哪些原因导致的。在此,我结合以前项目中遇到的多个问题场景,给大家做个详细的总结,以供借鉴或参考。

1、概述

       在项目代码中,我们会大量地使用C++标准库给我们提供的vector、list、map等STL标准模板库(Standard Template Library)容器。这些使用模版实现的容器列表很灵活、很好用,给我们日常编码带来了很大的便利。但在使用STL列表时可能会因为经验不足遇到这样或那样的问题。

       本文根据多年的项目问题排查经验以及遇到的多个问题场景,大概地总结一下操作STL列表时发生异常的常见原因,如下:

1)使用数组下标(仅能操作vector)或者使用迭代器直接去访问STL列表中的元素,但这些元素不存在,导致越界访问。一般是因为没有判断STL列表为空,或者没有判断要访问的元素是否存在。
2)在遍历STL列表删除元素时迭代器自加处理的有问题。
3)多线程同时操作STL列表时没有加锁保护,导致访问冲突。一般是一个线程在遍历STL列表,另一个线程在增删STL列表中的元素,导致访问冲突,出现异常崩溃。
4)结构体某个成员是STL列表对象,对结构体对象进行memset操作,破坏了STL内部的内存结构,导致访问STL列表时出异常。大家在定义结构体对象时,习惯性地进行了memset操作。但能进行memset的前提是,结构体中所有成员都必须是基本类型的!一旦结构体中包含C++类对象,是严禁进行memset操作的。


         在这里,给大家重点推荐一下我的几个热门畅销专栏:

专栏1:(该专栏订阅量已达到400个,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!)

C++软件调试与异常排查从入门到精通系列文章汇总icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据近几年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的实战问题分析实例,带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

专栏中的文章均是通过项目实战总结出来的(通过项目实战积累了大量的异常排查素材和案例),有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2: 

C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域的多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!

专栏3: 

开源组件及数据库技术icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html

以多年的开发实战为基础,分享一些开源组件及数据库技术!


2、使用STL列表中的元素越界

       比如我们在使用vector列表时,常使用数组下标的方式去访问列表中的元素,有时可能没有判断列表中元素个数,直接就使用下标去访问了,比如:

// 设备信息
struct TDeviceInfo
{char szDeviceId[64];   // 设备idchar szDeviceName[64]; // 设备名称char szIp[32];         // 设备ipunsigned short uPort;  // 设备端口int nDevType;          // 设备类型
};vector<TDeviceInfo> vtDevList;
int nDevType = vtDevlist[3].nDevType;

可能当前列表vtDevlist中的元素个数少于3个,但我们用数组下标的方式去强行访问了,导致Out of range访问越界了。

我们之所以通过数组下标去访问vector列表元素,不会平白无故地去访问,而是与我们的业务场景关联的。

       还有一种情况是使用迭代器去越界访问的

vector<TDeviceInfo>::iterator iter = vtDevList.begin();
int nDevType = iter->nDevType;

没有判断列表是否为空,强行去访问列表中的begin元素的。没有判断当前迭代器是否为end的,即

if (itor != vtDevList.end() )
{
}

这个问题场景,我们在项目中多次遇到过。

3、遍历STL列表删除元素时对迭代器自加处理有问题引发越界

       这是新人比较容易犯的错误。比如删除设备列表中设备类型等于1的所有设备,有问题的代码如下:

vector<TDeviceInfo> vtDevList;
vector<TDeviceInfo>::iterator iter = vtDevList.begin();
for (; iter != vtDevList.end(); iter++)
{if (iter->nDevType == 1){vtDevList.erase(iter);}
}

如果有元素被删除,在Debug下调试时,会报如下的错误:

       正确的写法是:

for (; iter != vtDevList.end(); )
{if (iter->nDevType == 1){iter = vtDevList.erase(iter);}else{iter++;}
}

当执行erase操作后,迭代器iter原先的指向就失效了,不能再对其进行++操作,否则就越界了。对于迭代器变量iter,要分开处理,如果有删除元素,则将erase接口返回的迭代器赋值给iter;如果没有删除元素,直接对迭代器进行++操作。

新人在写遍历STL列表删除元素的代码时可能会犯上面的错误,没有区分对待。

4、更隐蔽的遍历STL列表删除元素时引发越界的场景

        这个问题场景和上面讲的遍历STL列表删除元素的场景类似,但本场景更有隐蔽性,这在以前的项目中遇到过。

这个问题是同事那边遇到的,在调试程序时老是在遍历STL列表时出现异常,于是找我过去帮忙排查一下。

       问题场景是这样子的,在遍历STL列表的for循环体中调了一个函数fun1,因为业务比较复杂,涉及了多个函数的调用,fun1函数内部调用了fun2函数,fun2函数中调了fun3函数,fun3函数中调用了fun4函数,问题就出在fun4函数中,fun4函数中居然将当前最上面正在遍历的STL列表中的元素删除了,演示代码如下:

vector<TDeviceInfo> vtDevList;
vector<TDeviceInfo>::iterator iter = vtDevList.begin();
for (; iter != vtDevList.end(); iter++)
{// 调用fun1fun1();
}void fun2()
{// 调用fun3fun3();
}void fun3()
{// 调用fun4fun4();
}void fun4()
{// 此处删除了STL列表vtDevList中的内容
}

从而出现在遍历STL列表时删除列表中元素的场景。应该像上面那个场景中那样,处理代码:

for (; iter != vtDevList.end(); )
{if (iter->nDevType == 1){iter = vtDevList.erase(iter);}else{iter++;}
}

但实际上,因为当前问题场景中函数调用层次很深,不适合这样处理,只能将这种在遍历STL列表时删除元素的操作给取缔掉,重新编写代码控制逻辑。正是因为函数调用层次比较深,所以这个问题有很强的隐蔽性。

当时排查这个问题时,凭着经验,首先排除多线程同时操作同一个STL列表的场景,当时觉得大概率是遍历STL列表过程中有地方删除了STL列表中的元素导致的。然后沿着这个思路,最终在某个函数中找到了删除STL列表元素的代码。

5、多线程同时操作STL列表时没有加锁导致冲突

        多线程同时操作某个STL列表时产生问题的典型场景是,一个线程在遍历STL列表,另一个线程在对STL列表中的元素进行增删操作。一般在多线程操作共享资源时都要加锁,但加锁有个原则,尽量较小锁的范围,在锁的范围的代码要尽快执行完,不能影响其他线程的执行效率。

       这个问题,我们在项目中遇到不止一次了。有时可能比较具体隐蔽性,比如在另一个线程中调用某个函数接口,在该函数内部操作了一个公用的STL列表,然后出现了多线程同时操作STL列表的场景,有时较难发现这里面隐藏的多线程操作共享资源的问题,特别是在维护他人编写的代码模块时。

6、对包含STL列表对象的结构体进行memset操作导致STL列表对象内存出异常

       大家在定义结构体对象时,习惯性地在使用之前对结构体对象进行了memset操作。如果结构体中某个成员是STL列表对象,执行memse操作后,会破坏STL对象内部的内存结构,导致访问STL列表时出现异常。这个问题在项目中出现过,当时是一个新人写的代码(新人考虑问题不够全面,缺乏安全意识),因为缺乏经验,直接对一个包含STL列表对象的结构体进行memset操作,比如:(结构体TWindowInfo中包含了一个STL list成员)

typedef struct tagWindowInfo
{
public:HWND hWnd;                               // 当前窗口句柄char szWndName[256];                     // 当前窗口名称RECT m_rcWnd;                            // 当前窗口矩形坐标(在屏幕坐标系中)std::list<tagWindowInfo> childWndList;   // 当前窗口所有子窗口信息列表
}TWindowInfo;// 结构体TWindowInfo中包含了一个list列表成员,下面直接对结构体对象执行了memset操作
TWindowInfo tWndInfo;
memset(&tWndInfo, 0, sizeof(tWndInfo));	

导致程序在访问这个STL列表时出现了各种莫名其妙的错误,比如列表中的元素数据不对,执行STL列表类的成员函数发生异常崩溃。当时这个问题我查了很久,后来才发现是因为对结构体进行memset操作导致的。

      注意,能进行memset的前提是,结构体中所有成员都必须是基本类型的!一旦结构体中包含C++类对象,是严禁进行memset操作的。为什么不能对类对象进行memset操作呢?比如类中有虚函数,就会内置一个虚函数表指针,这个虚函数表指针中存放的是存放虚函数地址(代码段地址)的虚函数表,在调用虚函数时会用到这个虚函数表。如果对类对象进行memset操作,会将虚函数表指针中的值置为NULL,这样在调用虚函数时出现问题。对于虚函数调用的过程与机制,需要从汇编代码的角度去看,涉及到两次寻址,具体过程可以参见我之前写的文章:
几秒读懂C++虚函数调用的汇编代码实现icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/121046234       再比如,有些类实现的比较复杂,例如STL列表,类内部会有额外的变量去维护管理较复杂的内部内存结构,如果对这样的类对象进行memset操作,将维护内部内存结构的变量的值都只为NULL了,结果是不可预料(unexpected)的。 

         对STL列表执行memset操作引发异常的问题,我们在项目中遇到了两次,都是新人写的代码,两次的表现现象是不太一样的:

1)一次是在遍历STL列表读取列表中元素发现元素值异常。

2)一次是操作STL列表时产生了异常,直接将当前函数余下的代码都跳过了,导致该执行的代码没有执行,导致后续代码执行时逻辑出现异常。

       其实C++中的大部分问题都是内存问题,在分析这些问题时要从内存的角度去看。很多问题,从内存的角度去看,就好理解多了!变量本质上就是一块内存,给变量赋值就是向内存中写入内容,读变量的值就是读取内存中的内容,这一点从汇编代码上可以清晰地看出来!

7、最后

       在分析和解决问题时,大家要主动地多想想多思考,多倒腾倒腾里面的细节问题,必要的时候可以进行归纳与总结。很多细节知识点,都是相通的,搞懂一个细节,相关的细节点可能很快就能明白了!   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/767542.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python之(19)CPU性能分析常见工具

Python之(19)CPU性能分析常见工具 Author: Once Day Date: 2024年3月24日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏:Python开发_Once-Day的博客…

深度学习 tablent表格识别实践记录

下载代码&#xff1a;https://github.com/asagar60/TableNet-pytorch 下载模型&#xff1a;https://drive.usercontent.google.com/download?id13eDDMHbxHaeBbkIsQ7RSgyaf6DSx9io1&exportdownload&confirmt&uuid1bf2e85f-5a4f-4ce8-976c-395d865a3c37 原理&#…

查看文件内容的指令:cat,tac,nl,more,less,head,tail,写入文件:echo

目录 cat 介绍 输入重定向 选项 -b -n -s tac 介绍 输入重定向 nl 介绍 示例 more 介绍 选项 less 介绍 搜索文本 选项 head 介绍 示例 选项 -n tail 介绍 示例 选项 echo 介绍 输出重定向 追加重定向 cat 介绍 将标准输入(键盘输入)的内容打…

pta L1-077 大笨钟的心情

L1-077 大笨钟的心情 分数 15 退出全屏 作者 陈越 单位 浙江大学 有网友问&#xff1a;未来还会有更多大笨钟题吗&#xff1f;笨钟回复说&#xff1a;看心情…… 本题就请你替大笨钟写一个程序&#xff0c;根据心情自动输出回答。 输入格式&#xff1a; 输入在一行中给出…

【ZYNQ】基于ZYNQ 7020的OPENCV源码交叉编译

目录 安装准备 检查编译器 安装OpenCV编译的依赖项 下载OpenCV源码 下载CMake 编译配置 编译器说明 参考链接 安装准备 使用的各个程序的版本内容如下&#xff1a; 类别 软件名称 软件版本 虚拟机 VMware VMware-workstation-full-15.5.0-14665864 操作系统 Ub…

线性表的合并之求解一般集合的并集问题(单链表)

目录 1问题描述&#xff1a; 2问题分析&#xff1a; 3代码如下&#xff1a; 4运行结果&#xff1a; 1问题描述&#xff1a; 已知两个集合A和B&#xff0c;现要求一个新的集合AAuB。例如&#xff0c;设 A&#xff08;7&#xff0c;5&#xff0c;3&#xff0c;11&#xff09;…

基于Matlab的血管图像增强算法,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

设计数据库之外部模式:数据库的应用

Chapter5&#xff1a;设计数据库之外部模式&#xff1a;数据库的应用 笔记来源&#xff1a;《漫画数据库》—科学出版社 设计数据库的步骤&#xff1a; 概念模式 概念模式(conceptual schema)是指将现实世界模型化的阶段进而&#xff0c;是确定数据库理论结构的阶段。 概念模…

k8s笔记27--快速了解 k8s pod和cgroup的关系

k8s笔记27--快速了解 k8s pod和 cgroup 的关系 介绍pod & cgroup注意事项说明 介绍 随着云计算、云原生技术的成熟和广泛应用&#xff0c;K8S已经成为容器编排的事实标准&#xff0c;学习了解容器、K8S技术对于新时代的IT从业者显得极其重要了。 之前在文章 docker笔记13–…

【Web APIs】事件高级

目录 1.事件对象 1.1获取事件对象 1.2事件对象常用属性 2.事件流 1.1事件流的两个阶段&#xff1a;冒泡和捕获 1.2阻止事件流动 1.3阻止默认行为 1.4两种注册事件的区别 3.事件委托 1.事件对象 1.1获取事件对象 事件对象&#xff1a;也是一个对象&#xff0c;这个对象里…

电子电器架构 —— 诊断数据DTC具体故障篇

电子电器架构 —— 诊断数据DTC起始篇 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师 (Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,喝完再挣扎…

算法---前缀和练习-2(和为k的子数组)

和为k的子数组 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;点这里 2. 讲解算法原理 创建一个无序映射&#xff08;哈希表&#xff09; hash&#xff0c;用于统计前缀和的出现次数。初始时&#xff0c;将前缀和为 0 的次数设为 1&#xff0c;表示…

牛客题霸-SQL篇(刷题记录二)

本文基于前段时间学习总结的 MySQL 相关的查询语法&#xff0c;在牛客网找了相应的 MySQL 题目进行练习&#xff0c;以便加强对于 MySQL 查询语法的理解和应用。 由于涉及到的数据库表较多&#xff0c;因此本文不再展示&#xff0c;只提供 MySQL 代码与示例输出。 以下内容是…

HarmonyOS应用开发实战 - Api9 拍照、拍视频、选择图片、选择视频、选择文件工具类

鸿蒙开发过程中&#xff0c;经常会进行系统调用&#xff0c;拍照、拍视频、选择图库图片、选择图库视频、选择文件。今天就给大家分享一个工具类。 1.话不多说&#xff0c;先展示样式 2.设计思路 根据官方提供的指南开发工具类&#xff0c;基础的拍照、拍视频、图库选照片、选…

使用Python进行自动化测试Selenium与PyTest的结合【第150篇—自动化测试】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 使用Python进行自动化测试&#xff1a;Selenium与PyTest的结合 在软件开发中&#xff0c;自…

css盒子模型及浮动

内容(content)、内边距(padding)、边框(border)、外边距(margin) oder:1px solid red; 边框的粗细 边框的样式&#xff08;虚线还是实线&#xff09; 边框的颜色 border中也有一些属性可以直接调某一个方向上的边框的粗细&#xff0c;样式&#xff0c;颜色 border-left\bord…

2024/3/24 LED点阵屏

显示原理&#xff1a; 类似矩阵键盘&#xff0c;逐行or逐列扫描 74HC595是串行 寄存器 感觉就是三转八寄存器 并行&#xff1a;同时输出&#xff1b;串行&#xff1a;一位一位输出 先配置74HC595&#xff0c;重新进行位声明 sbit RCKP3^5; //RCLK sbit SCKP3^6; …

淘宝|天猫|京东|1688主流电商平台的实时数据返回接口|附Python实例

导读&#xff1a;随着淘宝/天猫直通车功能升级&#xff0c;很多功能越来越白盒化&#xff0c;越来越简化&#xff0c;更方便用户的操作&#xff0c;只需一键即可看出淘宝/天猫直通车存在的问题。淘宝/天猫直通车千人千面后有了实时数据工具&#xff0c;下面通过一个案例告诉大家…

23. UE5 RPG制作属性面板(一)

随着角色的属性越来越多&#xff0c;我们不能每次都进行showdebug abilitysystem进行查看&#xff0c;而且玩家也需要查看角色属性&#xff0c;所以需要一个查看玩家角色属性的面板。 在前面&#xff0c;我们创建三种类型的属性 Primary Attributes&#xff08;主要属性&#…

常见的OOM 问题的 6 种场景

今天跟大家一起聊聊线上服务出现 OOM 问题的 6 种场景,希望对你会有所帮助。 一、堆内存 OOM 堆内存 OOM 是最常见的 OOM 了。 出现堆内存 OOM 问题的异常信息如下: java.lang.OutOfMemoryError: Java heap space此 OOM 是由于 JVM 中 heap 的最大值,已经不能满足需求了…