Accurate in garbage collection means every effective references to managed objects are identified, thus the system can eventually find out all those unreachable objects. On the contrary, a conservative garbage collector will "conservatively" treat any possible integer values that maybe a pointer to an object as an effective pointer. So, in theory, it cannot guarantee it can collect all unreachable objects.
Once a collector conservatively thinks an unreachable object is live, then it will not execute any destructor or destructing behavior associated with the object. Some people might think, since the probability that a non-pointer integer accidentally looks like a pointer is very rare in practice, it is not a problem in the real world.
Unfortunately, the reality is not like what people expected. See following application example for Boehm-Demers-Weiser conservative garbage collector, you can compile, build with BDW collector, and run it. The output result will be: "failed", not the one "Hello" we expected.
#include <windows.h>
#include <stdio.h>
#define GC_DLL
#define GC_WIN32_THREADS
#include "gc.h"
class CFile {
private:
HANDLE m_hFile;
static void CleanUp(void *pObj, void *) {
CFile * _this = (CFile*)pObj;
_this->~CFile();
}
public:
CFile(HANDLE fp) : m_hFile(fp) {
GC_register_finalizer(this, CleanUp, 0, 0, 0);
}
~CFile() {
printf("Destruct CFile [%p]: CloseHandle(%p).\n", this, m_hFile);
CloseHandle(m_hFile);
}
operator HANDLE () {
return m_hFile;
}
void* operator new( size_t size ) { return GC_MALLOC(size); }
void operator delete( void* obj ) {}
};
void store()
{
CFile * pFile = new CFile(
CreateFile(
TEXT("myfile.txt"),
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL));
DWORD len;
WriteFile(*pFile, "Hello\n", 7, &len, 0);
pFile = 0; // this may not clear compiler generated hidden variable
GC_gcollect(); // garbage collect
}
void load() {
char buf[128];
CFile * pFile;
GC_gcollect(); // garbage collect
GC_gcollect(); // again
GC_gcollect(); // and again
pFile = new CFile(
CreateFile(
TEXT("myfile.txt"),
GENERIC_READ,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL));
DWORD len;
if (!ReadFile(*pFile, buf, 7, &len, 0)) {
printf("failed.\n");
} else {
printf("%s", buf);
}
}
int main () {
GC_INIT();
store();
load();
return 0;
}
In the above example, the class 'CFile' wrap a Win32 handle and automatically close the handle after use. Function 'store()' creates a new file and then write a string "Hello" into the file; Function 'load()' reopen the file and read the string from it. Idealistically, after the execution get out of the scope of 'store()' function, or after the statement 'pFile = 0;' is executed, there is no effective reference to the 'CFile' object. So garbage collection should detect that and call the destructor to close file handle.
But, in fact, all these several statements 'GC_gcollect();' do not collect the CFile object, so 'load()' cannot open the file for reading since the handle of last exclusive writing is not closed.
The reason is that, the collector conservatively think the object is reachable via some path, which is unknown to programmers. First, in 'store()' function, the 'pFile = 0;' statement does not guarantee that all references to the object in that function are dropped. Pointers in C/C++ are flat and like a value type as in .NET language, the compiler is free to make arbitrarily number of copies of them at anywhere it likes. So, programmers clean the 'pFile' variable doesn't mean to clean all the other copies that compiler made, the hidden copies references keep the object alive. Second, some old trash data in the execution stack may "seep" into the un-initialized variables, which will cause collector see the non-existing reference to the object. For example, the GC_register_finalizer() function may leave some trash data, such as 'this', referencing to the object in the stack. When 'load()' is running, the trash accidentally falls into the space of uninitialized variable 'buf' or 'pFile', then collector will see these trash as valid reference to the object, and falsely retain the object as it was live.
In that such simple program, there are so many things for us to concern. Larger application would make the situation more complicated and worse. A conservative collector cannot guarantee unreachable object be collected via certain collections, so defining a destructor or associating destructing behavior to an object is not dependable and of not much value.
Unaligned Pointer
Conservative garbage collection also depends on pointer alignment in application programs. That is, all pointers must be aligned on a natural boundary, e.g. 4 bytes boundary for 32-bit programs. If your program violates the rule, you will get crash as live objects will be collected as garbage. For example, if you define a class or structure having a compact packing setting than natural, then any unaligned pointer in it will be ignored by conservative collector, and will crash the application program.
#pragma pack(1)
struct CNode {
char m_bInUse;
CNode * m_pNext; // <-- unaligned pointer will cause crash
};
See Also:| About HnxGC |
|---|
| Overview |
| Download HnxGC |
| Installation |
| "Hello World" |
| Programming Guide |