0

我目前正在实现一个基本的垃圾收集器,其目的是在程序结束时删除所有左侧动态分配的对象。我希望类文档更清楚:

/**
 * 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 );
};

Object其他类将从中继承的这个基类会将分配内存的指针添加到这个 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 );
    }
};

这适用于新语句。但是如果尝试通过 new[] 分配一个数组,程序就会崩溃。Valgrind --leak-check=yes给出以下输出:

==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)

我调试了程序,gc 正试图删除 new[] 返回的地址处的内存。你能告诉我我的错在哪里吗?

4

3 回答 3

3

您不能将delete[]表达式与从返回的指针一起使用operator new[]。您必须operator delete[]直接使用。

这是因为new[]expression 有时会调整 的结果operator new[],而delete[]expression 会向相反方向调整参数所以:

  • 如果您有一个new[]表达式返回的指针,请使用表达式释放它delete[]
  • 如果您有一个由对 的调用返回的指针,请通过对 的调用来operator new[]释放它operator delete[]

一般来说,对于newexpression/ operator new/ deleteexpression/也是如此operator delete,但 GCC 可以让您摆脱这一点。

这是一个技术答案,仅涉及崩溃。注释中讨论了代码作为内存泄漏预防工具的有用性。

更新可以在http://ideone.com/0atp5找到一个快速的论文示例

请注意,如果您调用operator delete[]而不是delete[],

  • 不会调用析构函数(但如果您自动释放所有对象,则无论如何都不需要析构函数)
  • 您不需要将指针转换为Object*和返回,只需将所有内容存储为void*.
于 2012-04-12T14:56:22.220 回答
0

GarbageCollector::~GarbageCollector()调用Object::operator[] deleteand Object::operator delete,它会GarbageCollector::objects在迭代地图时更改地图。尝试先复制:

...
std::map< Object*, bool > objectsCopy = objects;
std::map< Object*, bool >::iterator it = objectsCopy.begin();
int counter = 0;
while( it != objectsCopy.end() ) {
...
于 2012-04-12T13:40:14.157 回答
0

如果您仅在程序结束时执行此操作,请禁止 remove() 调用以根本不删除它。该对象将在稍后立即销毁,无论如何地图将为空。

于 2012-04-12T14:40:17.070 回答