项目上有个需求要用到 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. Linux 内存管理 pt.1

    哈喽大家好,我是咸鱼 今天我们来学习一下 Linux 操作系统核心之一:内存 跟 CPU 一样,内存也是操作系统最核心的功能之一,内存主要用来存储系统和程序的指令.数据.缓存等 关于内存的学习,我会尽 ...

  2. 探讨AIGC的崛起历程,浅析其背后技术发展

    摘要:本文主要讨论了AIGC(人工智能生成内容)的发展历程.现状.应用,浅析其背后技术发展.与华为云的联系,以及面临的挑战和展望. 本文分享自华为云社区<AIGC:人工智能生成内容的崛起与未来展 ...

  3. Django中多个app放置同一文件夹中

    在pycharm中新建一个管理app的python package目录:apps 将存在的app用拖拽到apps目录下,此时会弹出对话框,取消勾选Search for references(搜索索引) ...

  4. uni-app 环境搭建

    环境搭建:下载Hbuilder X开发者工具 下载Hbuilderhttps://www.dcloud.io/index.htmlhttps://www.dcloud.io/hbuilderx.htm ...

  5. 【Java】连接MySQL问题总结

    前言 最近在学习Java的数据库相关操作,在看视频时自己找资源而产生的一些问题,在此做个总结. 正文 在刚开始学习的时候,你可能跟着老师这样写代码,虽然某些地方已经冒出了红色的波浪线,但你半信半疑的继 ...

  6. flutter填坑之旅(环境搭建篇)

    自从Google 在 2018 世界移动大会上发布 Flutter 的 Beta 版本,看看官方的介绍Flutter widgets are built using a modern framewor ...

  7. Python 中常见的 TypeError 是什么?

    翻译:BioIT 爱好者原文:TypeError: A Bytes-Like object Is Required, not 'str' | Finxter 简介 目标:在本教程中,我们的目标是修复以 ...

  8. RSA 加密解密

    from Crypto.Util.number import bytes_to_long, long_to_bytes, getPrime import libnum # 一.取两个素数 p = ge ...

  9. JavaWeb之Servlet详解(以及浏览器调用 Servlet 流程分析图)

    Servlet 1.什么是Servlet Servlet(java 服务器小程序) 他是由服务器端调用和执行的(一句话:是Tomcat解析和执行) 他是用java语言编写的, 本质就是Java类 他是 ...

  10. 第一章 : Linux入门

    1. 概述 ‍ ​​ ‍ 2. Linux 和 Windows 区别 ‍ ​​ ‍ 3. Centos 下载地址 ‍ 网易镜像:http://mirrors.163.com/centos/7/isos ...