今天在使用自己写的无锁线程安全环形队列给imu一个采集buffer的时候,出现了这样的奇怪现象:

我查明了原因:是因为我使用一个自己写的无锁环形队列时,

imu_data_array_.de_queue(imu_data)出列的时候没有给imu_data指定大小,导致malloc错误;当我在前面加上了imu_data.reserve(128)就没有出现malloc报错了;

但是令我感到疑惑的是,只要出现了filestream文件操作,才会引出malloc错误,否则不会因出该错误,这是为什么?这两个操作简直是没有一点关系

其中环形队列imu_data_array_出栈操作与filestream文件操作,似乎没有任何关系,但是往往就是这种牛头不对马嘴的关系引出来了潜在的内存泄漏问题!

Grok如是分析:

堆损坏的隐藏性

  • malloc 错误通常不是在内存分配的那一刻发生的,而是当程序试图使用损坏的堆区域时暴露出来的。

  • 在你的程序中,可能存在某个地方(比如队列的实现或其他代码)不小心造成了内存越界、重复释放或未初始化的指针操作,导致堆的内部数据结构被破坏。这种损坏可能在问题代码执行后并未立即显现

文件操作的影响

  • 文件操作(比如读写文件)通常涉及 I/O,会调用底层系统函数,这些函数可能会在堆上分配内存,比如用于缓存或临时缓冲区。

  • 当文件操作分配内存时,可能会改变堆的布局,或者触发堆管理器对堆状态的检查。如果堆已经被之前的错误损坏,这种检查就会暴露问题,导致 malloc 在后续分配时失败。

  • 而在没有文件操作时,堆的分配模式可能较为简单,损坏的区域没有被触及,因此错误暂时被“隐藏”了。

如此可见,代码中存在潜在的内存泄漏。

那么,在哪里泄漏呢?

valgrind --tool=memcheck --leak-check=full ./record

valgrind是linux上分析内存的强大助手,我这里借助于他来分析内存

运行输出情况为:

==56513== 
==56513== HEAP SUMMARY:
==56513==     in use at exit: 3,208 bytes in 3 blocks
==56513==   total heap usage: 12,966 allocs, 12,963 frees, 3,245,993 bytes allocated
==56513== *********************************************************************
==56513== 56 bytes in 1 blocks are definitely lost in loss record 1 of 3
==56513==    at 0x484AB64: operator new[](unsigned long) (vg_replace_malloc.c:431)
==56513==    by 0x11B99F: std::_MakeUniq<std::vector<double, std::allocator<double> > []>::__array std::make_unique<std::vector<double, std::allocator<double> > []>(unsigned long) (unique_ptr.h:968)
==56513==    by 0x11B6FB: LockFreeRingQueue<std::vector<double, std::allocator<double> > >::LockFreeRingQueue(unsigned int) (ring_queue.hpp:54)
==56513==    by 0x119F6B: LockFreeRingQueue<std::vector<double, std::allocator<double> > >::LockFreeRingQueue() (ring_queue.hpp:42)
==56513==    by 0x117C97: SensorSerialLoader::SensorSerialLoader(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int, int) (senorserialloader.hpp:14)
==56513==    by 0x116AD3: main (record.cpp:12)
==56513== *********************************************************************
==56513== 72 bytes in 1 blocks are definitely lost in loss record 2 of 3
==56513==    at 0x484AB64: operator new[](unsigned long) (vg_replace_malloc.c:431)
==56513==    by 0x21B2FB: std::_MakeUniq<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > []>::__array std::make_unique<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > []>(unsigned long) (unique_ptr.h:968)
==56513==    by 0x21AF77: LockFreeRingQueue<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::LockFreeRingQueue(unsigned int) (ring_queue.hpp:54)
==56513==    by 0x21A66F: LockFreeRingQueue<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::LockFreeRingQueue() (ring_queue.hpp:42)
==56513==    by 0x2198B7: serial::serial() (serial.cpp:11)
==56513==    by 0x117BDF: SensorSerialLoader::SensorSerialLoader(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int, int) (senorserialloader.hpp:14)
==56513==    by 0x116AD3: main (record.cpp:12)
==56513== *********************************************************************
==56513== 3,080 bytes in 1 blocks are definitely lost in loss record 3 of 3
==56513==    at 0x484AB64: operator new[](unsigned long) (vg_replace_malloc.c:431)
==56513==    by 0x11B99F: std::_MakeUniq<std::vector<double, std::allocator<double> > []>::__array std::make_unique<std::vector<double, std::allocator<double> > []>(unsigned long) (unique_ptr.h:968)
==56513==    by 0x11A0B7: LockFreeRingQueue<std::vector<double, std::allocator<double> > >::reserve(unsigned int) (ring_queue.hpp:135)
==56513==    by 0x117F3B: SensorSerialLoader::init() (senorserialloader.hpp:56)
==56513==    by 0x117CFF: SensorSerialLoader::SensorSerialLoader(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int, int) (senorserialloader.hpp:18)
==56513==    by 0x116AD3: main (record.cpp:12)
==56513== *********************************************************************
==56513== LEAK SUMMARY:
==56513==    definitely lost: 3,208 bytes in 3 blocks
==56513==    indirectly lost: 0 bytes in 0 blocks
==56513==      possibly lost: 0 bytes in 0 blocks
==56513==    still reachable: 0 bytes in 0 blocks
==56513==         suppressed: 0 bytes in 0 blocks
==56513== 
==56513== For lists of detected and suppressed errors, rerun with: -s
==56513== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

可见有三块没有回收的内存,分别为

  • 56字节的 SensorSerialLoader的构造函数初始化了一个LockFreeRingQueue对象,通过std::make_unique创建了std::vector<double>类型的数组

  • 72字节的SensorSerialLoader的构造函数触发的init函数调用了serial的构造函数初始化了一个LockFreeRingQueue对象,通过std::make_unique创建了std::string类型的数组

  • 3080字节的SensorSerialLoader的构造函数触发的init函数初始化了一个LockFreeRingQueue对象,通过std::make_unique创建了std::vector<double>类型的数组;这部分内存是reserve函数分配的,但没有被释放

从这三个泄漏来看,问题都指向了 LockFreeRingQueue 类的内存管理

  • 构造时分配的内存

    • 在构造函数中,std::make_unique 分配了数组(56 字节和 72 字节),但析构函数没有正确释放。

  • reserve 时重新分配的内存

    • 在 reserve 函数中,分配了新的缓冲区(3,080 字节),但旧缓冲区未释放,导致泄漏。

由于LockFreeRingQueue我没有写他的析构函数,所以先检查reserve函数

template <typename T>
void LockFreeRingQueue<T>::reserve(uint32_t size) {
    if(_queue != nullptr){
        _queue.release();
        _size = size <= 1U ? 2U : isPowerOfTwo(size) ? size :roundUpPowerOfTwo(size);
        _queue = std::make_unique<T[]>(_size);
        if(size == 0U) 
            throw std::out_of_range("Ring queue must be greater than 0");
    }
}

这里的第一句为 _queue.release() 我们来回想一下std::unique_ptr的用法:

release() 是 std::unique_ptr 的一个成员函数,它的作用是释放智能指针对管理对象的所有权,但不销毁对象。调用 release() 后,std::unique_ptr 不再持有该资源,返回指向该资源的原始指针。但是,不会删除资源,因此调用后你需要手动管理该资源的释放。

于是乎,问题明了了。

release() 将 std::unique_ptr 的所有权释放(指针变为空),但不会调用析构函数或删除底层内存。这意味着原来的 _queue 指向的内存变成了“裸指针”,失去了管理,导致内存泄漏

该问题也比较容易修改:

即去掉_queue.release(); 使_queue这个std::unique_ptr自己管理释放内存即可;

山和山不相遇,人与人要相逢