C++: free(): double free detected问题分析和处理

博客探讨了在C++编程中遇到的‘double freedetected’错误,该错误源于同一块内存被释放两次。通过分析代码示例,解释了问题的根本原因在于复制构造函数和析构函数导致的内存管理问题。提出了两种解决方案:一是自定义复制构造函数和赋值运算符以确保深拷贝;二是使用智能指针,如boost库的shared_array,以自动管理内存,避免手动释放导致的错误。使用valgrind工具验证了解决方案的有效性。

最近在项目中遇到了“free(): double free detected”问题,出问题的代码类似于:

#include <queue>
#include <cstring>
#include <iostream>
using namespace std;
 
class Test
{
    int *myArray;
 
public:
    Test() { myArray = new int[10]; }
 
    ~Test()
    {
        cout << "delete, myArray addr: " << myArray << endl;
        delete[] myArray;
    }
};
 
int main()
{
    queue<Test> q;
    Test t;
    q.push(t);
}

编译运行这段代码后,报错如下:

$ ./a.out
delete, myArray addr: 0x55f9d504a0d0
delete, myArray addr: 0x55f9d504a0d0
free(): double free detected in tcache 2
[1]    9912 abort (core dumped)  ./a.out

这里看到同一段堆内存被free了两次,使用valgrind工具检查也发现了frees比allocs多了一次:

$ valgrind --leak-check=yes ./a.out
==10533== HEAP SUMMARY:
==10533==     in use at exit: 0 bytes in 0 blocks
==10533==   total heap usage: 5 allocs, 6 frees, 74,344 bytes allocated
==10533==
==10533== All heap blocks were freed -- no leaks are possible
==10533==
==10533== For counts of detected and suppressed errors, rerun with: -v
==10533== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

出现这个问题的根本原因是 q.push(t), 当我们查看std::queue::push方法时,我们看到添加到队列的元素"初始化为 x 的副本"。它实际上是一个全新的对象,它使用复制构造函数复制原始对象的每个成员来制作一个新的 Test。默认情况下C++编译器会生成一个复制构造函数:当把所有变量值从旧对象复制到新对象时,就会有两个指针指向内存中的同一个数组。这本质上并不坏,但析构函数将尝试删除同一个数组两次,因此出现"double free detected"运行时错误。

解决方法一:添加copy constructor和copy-assignment operator

第一步是实现一个copy constructor,它可以安全地将数据从一个对象复制到另一个对象。它可能看起来像这样:

Test(const Test &other)
{
    myArray = new int[10];
    memcpy(myArray, other.myArray, 10);
}

现在在复制 Test 对象时,将为新对象分配一个新数组,并且也会复制数组的值。但是把Test对象赋值给另外一个对象时,编译器可能会导致类似的问题,所以我们要同样实现一个copy-assignment operator,看起来像是这样:

Test &operator=(const Test &other)
{
    myArray = new int[10];
    if (this != &other)
    {
        memcpy(myArray, other.myArray, 10);
    }
    return *this;
}

这里的重要部分是,我们将其他数组中的数据复制到此对象的数组中,使每个对象的内存保持独立,这样析构函数永远不会删除同一个数组两次。

解决方法二(推荐):使用智能指针

智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。我们尝试用boost库里面的智能指针来解决本文提到的问题,看起来像这样:

#include <queue>
#include <cstring>
#include <iostream>
#include <boost/shared_array.hpp>
using namespace std;
 
class Test
{
    int *myArray;
 
public:
    Test()
    {
        boost::shared_array<int> myArray1(new int[10]);
        myArray = myArray1.get();
        std::cout << "myArray addr " << myArray << std::endl;
    }
};
 
int main()
{
    queue<Test> q;
    Test t;
    q.push(t);
}

编译后使用valgrind工具检查可以发现堆内存的allocs和frees是一一对应的:

$ valgrind --leak-check=yes ./a.out
==21832== Memcheck, a memory error detector
==21832== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==21832== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==21832== Command: ./a.out
==21832==
myArray addr 0x5b7ff40
==21832==
==21832== HEAP SUMMARY:
==21832==     in use at exit: 0 bytes in 0 blocks
==21832==   total heap usage: 6 allocs, 6 frees, 74,376 bytes allocated
==21832==
==21832== All heap blocks were freed -- no leaks are possible
==21832==
==21832== For counts of detected and suppressed errors, rerun with: -v
==21832== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值