Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

overloading operator delete, or how to kill a cat?

I am experimenting with overloading operator delete, so that I can return a plain pointer to those who don't wish to work with smart pointers, and yet be able to control when the object is deleted.

I define a class Cat that is constructed with several souls, has an overloaded operator delete that does nothing, and destructor that decrements the number of souls (and also does some bragging). When souls reaches 0 the destructor calls the global ::delete, and the cat dies.

This sounds quite simple, but does not work as expected. Here's the code:

class Cat {
public:
    Cat(string n): name(n), souls(9)
    { cout << "Myaou... " << name << " is born\n"; }

    ~Cat();
    void operator delete(void *p) { cout << "!!! operator delete called\n"; }
    void report()
    { cout << name << "'s here, " << souls << " souls to spend\n"; }

    friend ostream& operator<< (const ostream& o, const Cat& cat);
private:
    void kill();
    const string name;
    int souls;
};

Cat::~Cat()
{
    cout << "!!! dtor called\n";
    kill();
}

void Cat::kill()
{
    if (--souls)
        cout << name << " is still alive! I have " << souls << " souls left.\n";
    else {
        cout << name << " is dying... good bye world!\n";
        ::delete((void*)this);
    }
}

ostream& operator<< (const ostream& o, const Cat& cat)
{
    return o << cat.name << "'s here, " << cat.souls << " souls to spend\n";
}

here's the main:

int main()
{
    Cat *p = new Cat("Mitzi");

    for (;;)
    {
        char c[100];
//      cout << *p;
        p->report();
        cout << "come on, hit me!";
        cin >> c;
        delete p;
    }
}

I'd expect that the loop would run for 9 times, and then something unpleasant (crash) would happen. However, this is the output:

Myaou... Mitzi is born
Mitzi's here, 9 souls to spend
come on, hit me!c
!!! dtor called
Mitzi is still alive! I have 8 souls left.
!!! operator delete called
's here, 8 souls to spend
come on, hit me!c
!!! dtor called
 is still alive! I have 7 souls left.
*** glibc detected *** /home/davidk/workspace/string_test/Debug/string_test: double free or corruption (fasttop): 0x080cd008 ***

Seems that after the first delete the name member is destroyed, and the next delete causes a crash. Any explanations? I am compiling with gcc on Linux, can be a compiler bug?

BTW, when I used the operator<<() as in cout << *p instead of repotr(), it was also weird: it entered an infinite loop of calling the constructor from within the operator<<(). What's going on here? :)

thanks!

like image 422
davka Avatar asked Nov 28 '22 10:11

davka


1 Answers

You've misused just every C++ concept possible and this causes errors. When delete p; is called for the first time C++ calls the Cat::~Cat() destructor which implicitly destroys the std::string inside the entity. When delete p; is called the second time Cat::~Cat() is rerun and it reruns the destructor of the std::string and this causes undefined behavour crashing the program because I suppose std::string::~string() doesn't nullify the pointer to the buffer and so when std::string::~string() tries to release the buffer for the second time with the same address this leads to famous double-free.

like image 79
sharptooth Avatar answered Dec 05 '22 02:12

sharptooth