0

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?

Unihedron
  • 10,902
  • 13
  • 62
  • 72
tbolender
  • 1,172
  • 3
  • 14
  • 19
  • 10
    The point of a garbage collector is to free unused allocated memory **while** the program is running, not at its end. When the program exits, all of its memory is freed anyway. Are you sure this is what you want to do? – George Skoptsov Apr 12 '12 at 12:42
  • duplicate of http://stackoverflow.com/questions/654754/what-really-happens-when-you-dont-free-after-malloc – XoCombo Apr 12 '12 at 12:53
  • Yes I am sure about this ;) This was the reason why I italicised it.. I just did not know any better name. It just should help me handle memory correct and notify me in the case I made a mistake so that I do not have to run valgrind everytime.. – tbolender Apr 12 '12 at 13:40
  • This thing does not prevent any memory leaks. It just hides them from `valgrind`. – n. m. could be an AI Apr 12 '12 at 13:47
  • But the memory will be freed by the program, isn't it? – tbolender Apr 12 '12 at 14:17
  • A memory leak is **not** memory that is not freed. It is memory that is **lost and cannot be reused by the program**. "Not freed" is just a useful approximation that can be checked by tools like `valgrind`. – n. m. could be an AI Apr 12 '12 at 14:33
  • TiBo, I once did something close to this which was an IAutoDestructable. For an embedded project, we didn't care what objects weren't actually deleted at the end of the program (as an embedded system it never ends) but we were interested in runtime memory leaks. So we added this interface to any class that should be destroyed on exit and registered clearing it to atexit(). Memory leak reports suddenly became a *lot* quicker to read and we lost about 20k lines of destructor code. – dascandy Apr 12 '12 at 14:38
  • @dascandy: why register anything to `atexit()` if the program never exits? – n. m. could be an AI Apr 12 '12 at 15:09
  • @n.m. the debug version does exit and just after that point the memory checker sees what memory was leaked. That was used to find runtime memory leaks. The production version never exited but would have runtime memory leaks so we wanted to find those. It also saved a pile of effectively dead code :-) – dascandy Apr 12 '12 at 20:10

3 Answers3

3

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*.
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Thank you very much for this explanation :) The changes of the code works pretty well (also with void* pointers). There is just one thing that makes me wonder: When I free the memory with `Object::operator delete[]( obj )` the destructor is called. Do you have any idea why? – tbolender Apr 12 '12 at 21:46
  • Um, I'm not sure why dtors are called in your program. They should not be and they are not not in mine. See http://ideone.com/FPKjn – n. m. could be an AI Apr 12 '12 at 22:09
  • Very ominous o.O I added output before and after the cleaning up loop and like you in the destructor. Between the before and after output the destructor message appears. – tbolender Apr 12 '12 at 22:30
0

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() ) {
...
user1202136
  • 11,171
  • 4
  • 41
  • 62
0

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.

dascandy
  • 7,184
  • 1
  • 29
  • 50