项目上有个需求要用到 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. I-o-C 一篇概览

    一.ioC 容器和 Bean介绍 IoC(Inversion of Control )也被称之为 DI(dependency injection),名称侧重点略有不同. 所谓控制翻转即对象通过构造函数 ...

  2. CS144 计算机网络 Lab4:TCP Connection

    前言 经过前面几个实验的铺垫,终于到了将他们组合起来的时候了.Lab4 将实现 TCP Connection 功能,内部含有 TCPReceiver 和 TCPSender,可以与 TCP 连接的另一 ...

  3. 代码随想录算法训练营Day36 贪心算法

    代码随想录算法训练营 代码随想录算法训练营Day36 贪心算法| 435. 无重叠区间 763.划分字母区间 56. 合并区间 435. 无重叠区间 题目链接:435. 无重叠区间 给定一个区间的集合 ...

  4. < Python全景系列-9 > Python 装饰器:优雅地增强你的函数和类

    欢迎来到我们的系列博客<Python全景系列>第九篇!在这个系列中,我们将带领你从Python的基础知识开始,一步步深入到高级话题,帮助你掌握这门强大而灵活的编程语法.无论你是编程新手,还 ...

  5. 基于SqlSugar的开发框架循序渐进介绍(31)-- 在查询接口中实现多表联合和单表对象的统一处理

    在一些复杂的业务表中间查询数据,有时候操作会比较复杂一些,不过基于SqlSugar的相关操作,处理的代码会比较简单一些,以前我在随笔<基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中 ...

  6. 从0搭建Vue3组件库(九):VitePress 搭建部署组件库文档

    VitePress 搭建组件库文档 当我们组件库完成的时候,一个详细的使用文档是必不可少的.本篇文章将介绍如何使用 VitePress 快速搭建一个组件库文档站点并部署到GitHub上 安装 首先新建 ...

  7. 【C#/.NET】Dapper使用QueryMultipleAsync执行多条SQL

    ​  目录 背景 解决方案 总结 背景 对于查询数据列表的功能,需要分页已经查询总数.这里涉及两句SQL,一个是查询分页对应的数据,第二个是Count(*); 会导致部分重复代码和两次的数据库查询. ...

  8. 将HTML网页转换为Markdown格式的工具及方法

    保存博客文章 早期在markdown语法还没有推出来之前,编写blog是在网页上或olw写的,也就是文章是保存在对方的主机上. 最近计划把我在博客园的一些早期html文章转换成markdown的文件, ...

  9. 前端Vue自定义简单好用商品分类列表组件 侧边栏商品分类组件

    前端Vue自定义简单好用商品分类列表组件 侧边栏商品分类组件 , 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=13148 效果 ...

  10. Redis6 的安装

    安装网址 ‍ Redis 官方网站 Redis 中文官方网站 http://redis.io http://redis.cn/ ‍ 安装版本 ‍ 6.2.1 for Linux(redis-6.2.1 ...