std::queue 中遇到释放内存错误的问题
项目上有个需求要用到 std::queue 顺序处理消息事件
简单的示例如下:
struct MyEvent {
MyEvent() { event_ = CreateEvent(nullptr, 0, 0, 0); }
~MyEvent() { std::cout << "MyEvent deconstruct" << std::endl; }
void Run() {
if (event_ != nullptr) {
SetEvent(event_);
}
}
private:
HANDLE event_;
};
int main() {
std::queue<MyEvent> my_event_queue;
HANDLE event = CreateEvent(nullptr, 0, 0, 0);
for (int i = 0; i < 3; i++) {
auto task = new MyEvent();
my_event_queue.push(*task);
}
while (!my_event_queue.empty()) {
auto my_event = &my_event_queue.front();
my_event_queue.pop();
delete my_event;
}
return 0;
}
测试案例上,我在队列 my_event_queue 上一共 push 了三次对象,随后使用 while 和 front 循环拿到队列中对象的地址并 pop
问题就是出在 delete my_event 上,理论上 std::queue 并不负责对象的析构,就是说你 new 的对象需要自己去 delete,所以我每 pop 一个对象出来后都 delete 一下
然后在 while 循环到第二次时就出现了 abort,一看内存,发现第二次 delete 时的内存是未分配的,故触发了 abort


从截图可以看出,句柄的大小是 4 个字节,也就是说在内存中分配是三个红框标出的地方,按照设想,每一次 delete 都应该抹除 4 个字节的内存区域,也就是第一次抹除第一个红框,第二次抹除第二个红框..
但实际上第一次 delete 就抹除了 20 个字节的内存长度,也就导致了第二次 delete 是访问到了未分配的内存
后续研究发现是因为 push 的时候传的是值而不是指针,导致 std::queue 调用了拷贝构造函数(没有显式定义拷贝构造函数就会调用默认的),所以队列中其实是保存的副本
每一次 pop 时都会主动析构掉副本,本体是不受影响的(需要我们手动 delete),故我们只是拿到了副本的指针并在 pop 后又 delete 了,此时的地址已经是悬空指针了,行为是不确定的
需要注意的是,20 个字节是队列的默认大小
怎么解决呢?
我们可以提前声明一个数组,里面放置 new 后的地址,在最后使用完毕后,依次 delete
MyEvent* task[3];
for (int i = 0; i < 3; i++) {
task[i] = new MyEvent();
my_event_queue.push(*task[i]);
auto task = new MyEvent();
my_event_queue.push(*task);
} ... // 此处只是方便测试
delete task[0];
delete task[1];
delete task[2];
当然更好的办法是使用智能指针来保证自动释放内存 std::queue<std::unique_ptr<MyEvent>> my_event_queue;
示例:
#include <Windows.h>
#include <synchapi.h> #include <iostream>
#include <memory>
#include <queue> struct MyEvent {
MyEvent() { event_ = CreateEvent(nullptr, 0, 0, 0); } // 添加移动构造函数
MyEvent(MyEvent&& other) : event_(other.event_) { other.event_ = nullptr; } ~MyEvent() {
if (event_ != nullptr) {
CloseHandle(event_); // 显式关闭句柄
}
std::cout << "MyEvent deconstruct" << std::endl;
} void Run() {
if (event_ != nullptr) {
SetEvent(event_);
}
} private:
HANDLE event_;
}; int main() {
std::queue<std::unique_ptr<MyEvent>> my_event_queue; for (int i = 0; i < 3; i++) {
auto task = std::make_unique<MyEvent>();
my_event_queue.push(std::move(task)); // 使用 std::move 将对象放入队列
} while (!my_event_queue.empty()) {
auto& my_event = my_event_queue.front();
my_event->Run();
my_event_queue.pop();
} return 0;
}
std::queue 中遇到释放内存错误的问题的更多相关文章
- C#中快速释放内存,任务管理器可查证
先close() 再dispose() 之后=null 最后GC.Collect() 如: ms.Close();//关闭流,并释放与之相关的资源 ms.Dispose();//如果是流的话,默认只会 ...
- 应用 AddressSanitizer 发现程序内存错误
作为 C/ C++ 工程师,在开发过程中会遇到各类问题,最常见便是内存使用问题,比如,越界,泄漏.过去常用的工具是 Valgrind,但使用 Valgrind 最大问题是它会极大地降低程序运行的速度, ...
- /MT、/MD编译选项,以及可能引起在不同堆中申请、释放内存的问题
一.MD(d).MT(d)编译选项的区别 1.编译选项的位置 以VS2005为例,这样子打开: 1) 打开项目的Property Pages对话框 2) 点击左侧C/C ...
- 【转】《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误
原文地址:http://blog.csdn.net/slvher/article/details/9150597 对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构 ...
- 错误内存【读书笔记】C程序中常见的内存操作有关的典型编程错误
题记:写这篇博客要主是加深自己对错误内存的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢. 对C/C++程序员来讲,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的 ...
- 析构函数释放内存时出现_BLOCK_TYPE_IS_VALID错误
错误信息截图: 原因: 1.内存泄漏:所以当程序退出时,系统会收回分配的内存,于是调析构函数,由于内存已被错误地释放,于是就会出现"Debug Assertion Failed"的 ...
- 《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误
对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的模块跑起来后才出现内存崩溃,是很让人痛苦的.因为崩溃的位置在时间和空间上,通常是在距真正的错误源一段距离之后才 ...
- 从deque到std::stack,std::queue,再到iOS 中NSArray(CFArray)
从deque到std::stack,std::queue,再到iOS 中NSArray(CFArray) deque deque双端队列,分段连续空间数据结构,由中控的map(与其说map,不如说是数 ...
- Objective-C 内存管理之dealloc方法中变量释放处理
本文转载至 http://blog.sina.com.cn/s/blog_a843a8850101ds8j.html (一).关于nil http://cocoadevcentral.com/d/ ...
- 因内存释放而引发的中断问题,dll中new的内存释放问题
调试程序,每次关闭一个界面就会弹出中断错误. 为了确认这个问题,我将出现问题那一段代码中的函数一个个屏蔽,以此来确认到底哪个函数出现问题,缩小范围: 最后我发现,只要屏蔽掉checkIfFingerI ...
随机推荐
- 海思码率控制相关参数调优(CBR/VBR)
1.CBR 海思相关参数调整(在Hisi板,cat /proc/umap/rc 可查看相关参数变化) 1.1 RC参数 1.2 VENC参数 VENC_PARAM_H264_CBR_S/VENC_PA ...
- Shell脚本编程(二)
Shell脚本编程(二) shell脚本编程中if.if else的使用以及一些常用到的操作符 if.if else使用方式: 1) if条件 if [ condition ...
- vue本地开发配置及项目部署
一, 二,本地模拟配置代理,请求qq音乐的接口数据 三,axios请求头封装 参考http://www.axios-js.com/zh-cn/docs/#%E4%BB% ...
- html/css 添加图片
通过img.src添加图片 添加一个img元素,设置content,会发现在IE.safari等浏览器内显示为空白. 一般我们使用img,是通过src来设置的,可以通过react的import图片添加 ...
- java中各引用类型的生存时间
引用类型由上往下一次减弱: 强引用:Object obj=new Object(),无论什么情况下,只要强引用关系还存在,就不会回收被引用的对象. 软引用:像系统中缓存这些,在系统即将报内存溢出异常时 ...
- Rsync文件同步及备份
Rsync文件同步及备份 目录 Rsync文件同步及备份 Rsync基本概述 远程文件传输 服务端口 Rsync的三种传输模式 本地方式(类似cp) 远程方式(类似scp) 守护进程(C/S结构) R ...
- Oracle 定时任务job实际应用
目录 一.Oracle定时任务简介 二.dbms_job涉及到的知识点 三.初始化相关参数job_queue_processes 四.实际创建一个定时任务(一分钟执行一次),实现定时一分钟往表中插入数 ...
- 代码随想录算法训练营Day46 动态规划
代码随想录算法训练营 代码随想录算法训练营Day46 动态规划| ● 139.单词拆分 关于多重背包,你该了解这些! 背包问题总结篇! 139.单词拆分 题目链接:139.单词拆分 给定一个非空字符 ...
- 神经网络初步(Neural Network)——思想 具体实例以及代码实现
在前面我们详细的讨论过softmax损失函数以及SVM损失函数,以及应用了支持向量机进行图片分类的任务,不妨先复习一下支持向量机相关的思想内核:支持向量机想要寻求一组映射关系f(x)=wx+b,先将每 ...
- 如何让ChatGPT生成Midjourney提示词
导读:最近AI绘画非常的火,今天我们看ChatGPT如何生成Midjourney提示词,让AI教AI做事. 本文字数:900,阅读时长大约:3分钟 正如 Midjourney 的官方网站报道的那样 ...