首页 > 系统相关 >std::queue 中遇到释放内存错误的问题

std::queue 中遇到释放内存错误的问题

时间:2023-07-28 12:11:18浏览次数:27  
标签:std task MyEvent queue 内存 my event delete

项目上有个需求要用到 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,task,MyEvent,queue,内存,my,event,delete
From: https://www.cnblogs.com/strive-sun/p/17587258.html

相关文章

  • MIL-STD-1553B总线通信模块(1553B板卡)
    MIL-STD-1553B总线通信模块(1553B板卡)产品具有以下特点:1.产品覆盖多种接口CPCI/PXI/PCI/PC104/PC104+/USB等,满足用户不同平台的使用要求;2.自主知识产权IP核,通信速率支持1M/4M......
  • linux内存日志 | journalctl指令
    摘要一、linux内存日志就是有些日志仅仅在系统允许过程中写在内存当中,但是并不会保存到硬盘当中重启后,内存日志就会情况二、指令指令功能说明选项journalctl查看全部journalctl-n3查看最新3条journalctl--since19:00--until19:10:10查看起始......
  • Redis 过期删除策略与内存淘汰策略的区别及常用命令解析
    Redis是一种快速、高效的开源内存数据库,广泛应用于缓存、会话存储和实时数据处理等场景。为了维护数据的有效性和保证内存的合理利用,Redis引入了过期删除策略和内存淘汰策略。本文将深入探讨这两种策略的区别,同时解析与之相关的常用Redis命令,帮助读者更好地理解Redis在数据管理中的......
  • free -h查看内存情况,发现free部分远小于available
    原因是buff/cache占用了大量内存,需要手动释放下:echo3>/proc/sys/vm/drop_caches#参数说明:#0:不释放(系统默认值)#1:释放页缓存#2:释放dentries和inodes#3:释放所有缓存CacheMemory(缓存内存)当读写文件的时候,Linux内核为了提高读写性能与速度,会将文件在内存中进行缓存,这部分......
  • java 项目整合rabbitmq后内存飙高
    Java项目整合RabbitMQ后内存飙高的原因及解决办法在Java项目中使用RabbitMQ作为消息队列的时候,有时候会出现内存飙高的问题,特别是在消息量较大的情况下。本文将探讨这个问题的原因,并提供解决办法。问题分析当Java项目整合RabbitMQ后,内存飙高的原因通常是由于消息的生......
  • linux常用内存相关命令总结
    查看某个pid占用物理内存的峰值 cat/proc/pid/status|grep-E"VmHWM|VmRSS"参考信息:(23条消息)Linux下查看某一进程占用的内存_Jeremy_Lee123的博客-CSDN博客 内核内存泄漏常用工具kmemleakKmemleak是Linux内核提供的一个内存泄漏检测工具(内核3.1.5之后得版本支......
  • java启动jar包修改JVM默认内存问题
    JVM默认物理内存JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每......
  • c++ std::thread::joinable
    std:......
  • kube-apiserver内存溢出问题调查及go tool pprof工具的使用
    问题描述测试集群三台master,每个master上面的kube-apiserver都频繁的重启。登录其中一台master,发现kube-apiserver的内存占用特别高,每次重启完后内存很快就飙到了20G左右,而且还有继续增长的趋势。因为默认kube-apiserver的静态pod是没有设置memeorylimit的,最终api-server会吃光......
  • std::optional 内存布局
    对于std::optional<int>对应的内存布局为structoptional_mem{ int_M_payload; bool_M_engaged;};可以通过godbolt通过pahole工具查看在汇编窗口选择pahole右侧会显示数据结构classexception_ptr{ void*_M_exception_object;/*0......