tbolender
tbolender

Reputation: 1202

Overridding delete[] Operator?

I am currently implementing a basic garbage collector which purpose it is to delete all left dynamically allocated objects at the end of program. I hope the class documentation makes it more clear:

/**
 * This basic garbage collector provides easy memory leak prevention.
 * Just derive your class from utils::Object and the collector will
 * care about freeing dynamically allocated objects. This basic
 * implementation will just free all internal cached objects at the end
 * of program. So allocated memory which was not freed manually will
 * stay blocked up to this point.
 */
class GarbageCollector {
private:
    // All object collector should care about (true means array)
    std::map< Object*, bool > objects;

    GarbageCollector();
    /**
     * Free left allocated memory.
     */
    ~GarbageCollector() {
        std::map< Object*, bool >::iterator it = objects.begin();
        int counter = 0;
        while( it != objects.end() ) {
            // save pointer and iterate to next because
            // delete will remove obj from object set
            Object* obj = it->first;
            bool array = it->second;
            ++it;
            ++counter;
            if( array ) {
                delete[] obj;
            }
            else
                delete obj;
        }
        if( counter )
            std::cout << "GC: " << counter << " object(s) freed\n";
    }
public:
    /**
     * @return Static collector instance.
     */
    static GarbageCollector& getCollector();

    void addObject( Object* obj );
    void addArray( Object* obj );
    void remove( Object* obj );
};

This base class Object from which other classes will inherit from will add the pointer of the allocated memory to this gc:

class Object {
public:
    void* operator new( std::size_t size ) {
        void* ptr = malloc( size );
        if( !ptr )
            throw std::bad_alloc();
        GarbageCollector::getCollector().addObject( static_cast<Object*>(ptr) );
        return ptr;
    }
    void operator delete( void* ptr ) {
        GarbageCollector::getCollector().remove( static_cast<Object*>(ptr) );
        free( ptr );
    }
    void* operator new[]( std::size_t size ) {
        void* ptr = malloc( size );
        if( !ptr )
            throw std::bad_alloc();
        GarbageCollector::getCollector().addArray( static_cast<Object*>(ptr) );
        return ptr;
    }
    void operator delete[]( void* ptr ) {
        GarbageCollector::getCollector().remove( static_cast<Object*>(ptr) );
        free( ptr );
    }
};

This works fine for the new statement. But if try to allocate an array via new[] the program crashes. Valgrind --leak-check=yes gives this output:

==3030== Invalid read of size 8
==3030==    at 0x408305: utils::GarbageCollector::~GarbageCollector() (garbageCollector.cpp:40)
==3030==    by 0x55A4820: __run_exit_handlers (exit.c:78)
==3030==    by 0x55A48A4: exit (exit.c:100)
==3030==    by 0x558A313: (below main) (libc-start.c:258)
==3030==  Address 0x5b8e038 is 8 bytes before a block of size 144 alloc'd
==3030==    at 0x4C28F9F: malloc (vg_replace_malloc.c:236)
==3030==    by 0x409A59: utils::Object::operator new[](unsigned long) (object.cpp:45)
==3030==    by 0x409B58: start() (main.cpp:49)
==3030==    by 0x409C30: main (main.cpp:54)
==3030== 
==3030== Invalid free() / delete / delete[]
==3030==    at 0x4C282E0: free (vg_replace_malloc.c:366)
==3030==    by 0x409AE8: utils::Object::operator delete[](void*) (object.cpp:54)
==3030==    by 0x408339: utils::GarbageCollector::~GarbageCollector() (garbageCollector.cpp:40)
==3030==    by 0x55A4820: __run_exit_handlers (exit.c:78)
==3030==    by 0x55A48A4: exit (exit.c:100)
==3030==    by 0x558A313: (below main) (libc-start.c:258)
==3030==  Address 0x5b8e038 is 8 bytes before a block of size 144 alloc'd
==3030==    at 0x4C28F9F: malloc (vg_replace_malloc.c:236)
==3030==    by 0x409A59: utils::Object::operator new[](unsigned long) (object.cpp:45)
==3030==    by 0x409B58: start() (main.cpp:49)
==3030==    by 0x409C30: main (main.cpp:54)
==3030== 
GC: 1 object(s) freed
==3030== 
==3030== HEAP SUMMARY:
==3030==     in use at exit: 144 bytes in 1 blocks
==3030==   total heap usage: 8 allocs, 8 frees, 896 bytes allocated
==3030== 
==3030== 144 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3030==    at 0x4C28F9F: malloc (vg_replace_malloc.c:236)
==3030==    by 0x409A59: utils::Object::operator new[](unsigned long) (object.cpp:45)
==3030==    by 0x409B58: start() (main.cpp:49)
==3030==    by 0x409C30: main (main.cpp:54)
==3030== 
==3030== LEAK SUMMARY:
==3030==    definitely lost: 144 bytes in 1 blocks
==3030==    indirectly lost: 0 bytes in 0 blocks
==3030==      possibly lost: 0 bytes in 0 blocks
==3030==    still reachable: 0 bytes in 0 blocks
==3030==         suppressed: 0 bytes in 0 blocks
==3030== 
==3030== For counts of detected and suppressed errors, rerun with: -v
==3030== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 4 from 4)

I debugged the program an the gc is trying to delete the memory at the adress which new[] returned. Can you tell me where my fault is?

Upvotes: 0

Views: 2092

Answers (3)

n. m. could be an AI
n. m. could be an AI

Reputation: 119847

You cannot use delete[] expression with a pointer returned from operator new[]. You must use operator delete[] directly.

This is because new[] expression sometimes adjusts the result of operator new[], and delete[] expression adjusts the argument in the opposite direction So:

  • If you have a pointer returned by a new[] expression, free it with a delete[] expression.
  • If you have a pointer returned by a call to operator new[], free it with a call to operator delete[].

In general, this is also true with respect to new expression/operator new/delete expression/operator delete, but GCC lets you get away with this.

This is a technical answer, concerning only the crash. The usefulness of the code as a memory leak prevention tool is discussed in comments.

Update A quick an dirty example of the thesis can be found at http://ideone.com/0atp5

Please note that if you call operator delete[] instead of delete[],

  • The destructors will not be called (but if you free all objects automatically, destructors are not needed anyway)
  • You do not need to cast pointers to Object* and back, just store everything as a void*.

Upvotes: 3

dascandy
dascandy

Reputation: 7292

If you do this only at the end of the program, inhibit the remove() calls to not remove it at all. This object will be destroyed an instant later and the map will be empty regardless.

Upvotes: 0

user1202136
user1202136

Reputation: 11547

GarbageCollector::~GarbageCollector() calls Object::operator[] delete and Object::operator delete, which change the GarbageCollector::objects map, while iterating over it. Try making a copy first:

...
std::map< Object*, bool > objectsCopy = objects;
std::map< Object*, bool >::iterator it = objectsCopy.begin();
int counter = 0;
while( it != objectsCopy.end() ) {
...

Upvotes: 0

Related Questions