项目上有个需求要用到 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 中遇到释放内存错误的问题的更多相关文章

  1. C#中快速释放内存,任务管理器可查证

    先close() 再dispose() 之后=null 最后GC.Collect() 如: ms.Close();//关闭流,并释放与之相关的资源 ms.Dispose();//如果是流的话,默认只会 ...

  2. 应用 AddressSanitizer 发现程序内存错误

    作为 C/ C++ 工程师,在开发过程中会遇到各类问题,最常见便是内存使用问题,比如,越界,泄漏.过去常用的工具是 Valgrind,但使用 Valgrind 最大问题是它会极大地降低程序运行的速度, ...

  3. /MT、/MD编译选项,以及可能引起在不同堆中申请、释放内存的问题

    一.MD(d).MT(d)编译选项的区别 1.编译选项的位置 以VS2005为例,这样子打开: 1)         打开项目的Property Pages对话框 2)         点击左侧C/C ...

  4. 【转】《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误

    原文地址:http://blog.csdn.net/slvher/article/details/9150597 对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构 ...

  5. 错误内存【读书笔记】C程序中常见的内存操作有关的典型编程错误

    题记:写这篇博客要主是加深自己对错误内存的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢. 对C/C++程序员来讲,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的 ...

  6. 析构函数释放内存时出现_BLOCK_TYPE_IS_VALID错误

    错误信息截图: 原因: 1.内存泄漏:所以当程序退出时,系统会收回分配的内存,于是调析构函数,由于内存已被错误地释放,于是就会出现"Debug Assertion Failed"的 ...

  7. 《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误

    对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的模块跑起来后才出现内存崩溃,是很让人痛苦的.因为崩溃的位置在时间和空间上,通常是在距真正的错误源一段距离之后才 ...

  8. 从deque到std::stack,std::queue,再到iOS 中NSArray(CFArray)

    从deque到std::stack,std::queue,再到iOS 中NSArray(CFArray) deque deque双端队列,分段连续空间数据结构,由中控的map(与其说map,不如说是数 ...

  9. Objective-C 内存管理之dealloc方法中变量释放处理

    本文转载至 http://blog.sina.com.cn/s/blog_a843a8850101ds8j.html   (一).关于nil http://cocoadevcentral.com/d/ ...

  10. 因内存释放而引发的中断问题,dll中new的内存释放问题

    调试程序,每次关闭一个界面就会弹出中断错误. 为了确认这个问题,我将出现问题那一段代码中的函数一个个屏蔽,以此来确认到底哪个函数出现问题,缩小范围: 最后我发现,只要屏蔽掉checkIfFingerI ...

随机推荐

  1. mysql安装my.cnf配置

    进入my.cnf文件//乱码修改 设置编码 character_set_server = utf8mb4 //编码 collation-server = utf8mb4_general_ci //连接 ...

  2. html/css 添加图片

    通过img.src添加图片 添加一个img元素,设置content,会发现在IE.safari等浏览器内显示为空白. 一般我们使用img,是通过src来设置的,可以通过react的import图片添加 ...

  3. 在算数运算中,能否将 bool 值 true 视作 1?

    true == 1; true + 1; If the destination type is bool, see 4.12. If the source type is bool, the valu ...

  4. 2023-02-13:力扣数据中心有 n 台服务器,分别按从 0 到 n-1 的方式进行了编号 它们之间以「服务器到服务器」点对点的形式相互连接组成了一个内部集群 其中连接 connections 是

    2023-02-13:力扣数据中心有 n 台服务器,分别按从 0 到 n-1 的方式进行了编号 它们之间以「服务器到服务器」点对点的形式相互连接组成了一个内部集群 其中连接 connections 是 ...

  5. 2021-07-17:一个不含有负数的数组可以代表一圈环形山,每个位置的值代表山的高度。比如, {3,1,2,4,5}、{4,5,3,1,2}或{1,2,4,5,3}都代表同样结构的环形山。山峰A和山

    2021-07-17:一个不含有负数的数组可以代表一圈环形山,每个位置的值代表山的高度.比如, {3,1,2,4,5}.{4,5,3,1,2}或{1,2,4,5,3}都代表同样结构的环形山.山峰A和山 ...

  6. APRIL 2022-Explanation-Aware Experience Replay in Rule-Dense Environments

    I. INTRODUCTION 解释是人类智能的关键机制,这种机制有可能提高RL代理在复杂环境中的表现 实现这一目标的一个核心设计挑战是将解释集成到计算表示中.即使在最小的规则集变化下,将规则集(或部 ...

  7. 华为Atlas 200I DK A2开箱!

    摘要:Atlas 200I DK A2是Atlas 200DK之后的一款产品,从2022年一直酝酿至今,终于在2023年5月6日-7日昇腾AI开发者峰会2023正式发布. 本文分享自华为云社区< ...

  8. 人手一个 Midjourney,StableStudio 重磅开源!

    人手一个 Midjourney,StableStudio 重磅开源! Stability AI 公司在上个月 19 号推出了 Alpha 版本 StableLM 大语言模型,包含了 30 亿和 70 ...

  9. NIST SP 800-37 信息系统和组织的风险管理框架 安全和隐私的系统生命周期方法

    NIST SP 800-37 信息系统和组织的风险管理框架 安全和隐私的系统生命周期方法 它的结构分为3个层级:组织视图.业务任务和信息系统视图. 800-37是NIST SP 800-37的简称,即 ...

  10. 客户线上反馈:从信息搜集到疑难 bug 排查全流程经验分享

    写在前面:本文是我在前端团队的第三次分享,应该很少会有开发者写客户反馈处理流程以及 bug 排查的心得技巧,全文比较长,写了一个多星期大概1W多字(也是我曾经2年工作的总结),如果你有耐心阅读,我相信 ...