free():在调用重载赋值运算符时在 tcache 2 中检测到双重空闲

Posted

技术标签:

【中文标题】free():在调用重载赋值运算符时在 tcache 2 中检测到双重空闲【英文标题】:free(): double free detected in tcache 2 on calling overloaded assignment operator 【发布时间】:2021-10-22 14:30:25 【问题描述】:

我正在从事一个大学项目,我们将在其中将一些 c++ 字符串类实现为 Mystring。我正在研究重载赋值运算符,这是它的当前代码:

Mystring& Mystring::operator=(const Mystring& orig)

    if(this != &orig)
    
        delete ptr_buffer;
        len = orig.len;
        buf_size = orig.buf_size;
        ptr_buffer = orig.ptr_buffer;
    
    return *this;

ptr_buffer,长度。和 buf_size 是 Mystring 类的三个私有变量。这是我要测试的主要程序:

void check (const Mystring s, const string name)

    cout << "checking " << name << endl;
    cout << name << " contains " << s << endl;
    cout << name << " capacity() is " << s.capacity() << endl;
    cout << name << " length() is " << s.length() << endl;
    cout << name << " size() is " << s.size() << endl;
    cout << name << " max_size() is " << s.max_size() << endl << endl;



int main()

    Mystring s1("Hi there!");
    check(s1, "s1");

    Mystring s2("Testing before assignment!");
    check(s2, "s2");
    s2 = s1;
    check(s2, "s2");
    return 0;

这是输出的内容:

checking s1
s1 contains Hi there!
s1 capacity() is 10
s1 length() is 9
s1 size() is 9
s1 max_size() is 1073741820

checking s2
s2 contains Testing before assignment!
s2 capacity() is 27
s2 length() is 26
s2 size() is 26
s2 max_size() is 1073741820

checking s2
s2 contains Hi there!
s2 capacity() is 10
s2 length() is 9
s2 size() is 9
s2 max_size() is 1073741820

free(): double free detected in tcache 2

Process finished with exit code 134 (interrupted by signal 6: SIGABRT)

如您所见,赋值确实可以设置所有成员变量,但是我得到一个非零退出代码和 free(): double free detected in tcache 2 错误。我做错了什么,这个错误是什么意思?我已经确认退出代码来自调用分配,因为当我注释掉 s2 = s1; 时,它以退出代码 0 完成。

【问题讨论】:

delete ptr_buffer; 应该是 delete[] ptr_buffer; 如果你使用 new[] @Frank 是的,它是由我们的讲师提供的 orig.ptr_buffer 仍然设置。当orig被销毁时,想必它的析构函数会删除它 复制后你有 2 个指向同一个缓冲区的指针。你需要ptr_buffer = new char[buf_size];并从orig.ptr_buffer复制数据 @DrakeFord 不,这行不通。 如果缓冲区中有空间,它将起作用。您需要首先new[] 一个新缓冲区 - 但请考虑一下:如果在orig 中没有capacitysize,您只需要delete[] 缓冲区。如果您已经有容量,请不要delete[],只需从orig 复制-如果您需要delete[],您还需要new[orig.buf_size]-并且不要忘记相应地调整capacity 【参考方案1】:

在这个复制赋值运算符中

Mystring& Mystring::operator=(const Mystring& orig)

    if(this != &orig)
    
        delete ptr_buffer;
        len = orig.len;
        buf_size = orig.buf_size;
        ptr_buffer = orig.ptr_buffer;
    
    return *this;

至少有两个问题。如果指针ptr_buffer 指向的字符数组是动态分配的,那么您必须使用运算符delete [] 而不是delete

delete [] ptr_buffer;

第二个问题是这个赋值之后

ptr_buffer = orig.ptr_buffer;

两个指针指向同一个动态分配的内存。

你需要分配一个新的extent内存并复制分配对象的字符串。

操作符至少可以通过以下方式定义

Mystring & Mystring::operator =( const Mystring &orig )

    if(this != &orig)
    
        if ( buf_size != orig.buf_size )
        
            delete [] ptr_buffer;
            ptr_buffer = new char[orig.buf_size];
            buf_size = orig.buf_size;
         
        len = orig.len;
        strcpy( ptr_buffer, orig.ptr_buffer );
        // or if the class does not store strings then
        // memcpy( ptr_buffer, orig.ptr_buffer, len ); 
    

    return *this;

【讨论】:

nitpick:注释应为“不存储 null-terminated 字符串”。标准的std::string,允许字符串中间有空值就好了。【参考方案2】:

大概,Mystring 有一个析构函数,看起来像:

Mystring::~Mystring() 
  delete[] ptr_buffer;

因此,您不能只从orig 获取ptr_buffer 的所有权而不用其他东西替换它。这在 move-assignment 中是可行的,但由于您处于 copy-assignment 中,因此您必须保持 orig 原样。

这意味着您必须将orig 的数据复制到其中,并在必要时分配一个新数据:

Mystring& Mystring::operator=(const Mystring& orig)

    if(this != &orig)
    
        std::size_t min_buf_size = orig.len + 1; // or perhaps orig.buf_size, it depends.

        // no need to allocate a new buffer if the current one is big enough already.
        if(buf_size < min_buf_size) 
          delete[] ptr_buffer;
          buf_size = min_buf_size;
          ptr_buffer = new char[buf_size];
        

        len = orig.len;

        assert(buf_size >= len + 1);
        std::memcpy(ptr_buffer, orig.ptr_buffer, len + 1);
    
    return *this;

【讨论】:

以上是关于free():在调用重载赋值运算符时在 tcache 2 中检测到双重空闲的主要内容,如果未能解决你的问题,请参考以下文章

C++基础-5-运算符重载(加号,左移,递增,赋值,关系,函数调用)

C++ ——赋值运算符重载函数

赋值函数(运算符重载)

在 C# 中重载复合赋值运算符的简单方法?

C++中的重载赋值运算符

c++中为啥赋值运算符重载返回类型是引用