Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does explicitly calling a destructor cause double freeing?

Tags:

c++

The code below ends up with free(): double free detected in tcache 2. I want to know the inner process the error happens through.

#include <string>

class test
{
private:
    std::string member;
public:
    test(const std::string & arg) { member = arg; };
};

int main()
{
    test T = test("test test test test");
    T.~test();
    return 0;
}

The error depends on the length of the given string; if you change "test test test test" to "test", the program runs fine, at least with g++ on my computer.

like image 280
TrulyBright Avatar asked Dec 14 '22 07:12

TrulyBright


2 Answers

std::string has an internal buffer for short strings to avoid memory allocations. If the string is short, then there is no memory to free and it works. If the string is long it tries to free the buffer each time you call the destructor.

Here is more information on the Short String Optimization: Meaning of acronym SSO in the context of std::string

like image 74
Buddy Avatar answered Feb 24 '23 17:02

Buddy


In your code test T is a local variable and local variables are deleted when they go out of scope. If you call T.~test() explicitly then it deletes the variable before it goes out of scope. But when the variable really goes out of scope, the destructor is called again.

Consider the example (taken from your code):

#include <string>
#include <iostream>
class test
{
private:
    std::string member;
public:
    test(const std::string & arg) { member = arg; };
    ~test()
    {
        std::cout << " calling destructor "<<std::endl;
    }
};

int main()
{
    test T = test("test test test test");
    //take the call away and you will still have the destructor call
    //T.~test();
    return 0;
}

If now, you call the destructor explicitly, then you get the destructor called twice (once explicitly and once when the variable goes out of scope)

#include <string>
#include <iostream>
class test
{
private:
    std::string member;
public:
    test(const std::string & arg) { member = arg; };
    ~test()
    {
        std::cout << " calling destructor "<<std::endl;
    }
};

int main()
{

    test T = test("test test test test");
    //causing a double call to the destructor
    T.~test();
    return 0;
}

The execution would be

 calling destructor 
 calling destructor 
free(): double free detected in tcache 2
Abandon

If instead, you have allocated a pointer on test with new, you then need to explicitly call the destructor otherwise, you will have memory leaks.

When excuting the code below, you see that the destructor does not get called even if your pointer goes out of scope.

int main()
{

    test* T = new test("test test test test");
    // in this case you will have no more destructor automatically
    //T.~test();
    return 0;
}

You need to deallocate the allocated memory explicitly by calling the destructor but not by using T.~test() because now T is not of type test but test*. You only need to call delete T like this:

int main()
{

    test* T = new test("test test test test");
    //You need to deallocate the allocated memory explicitly
    //T.~test();
    delete T;
    return 0;
}

And the destructor is called on the allocated memory

like image 27
Pat. ANDRIA Avatar answered Feb 24 '23 19:02

Pat. ANDRIA