Friday, May 18, 2007
How to avoid GC-Hole
To avoid GC-Hole one must use GCPROTECT_BEGIN to keep your references up to date.
Here’s how to fix our buggy code fragment.
MethodTable *pNT = g_pObjectClass->GetRefTable();
OBJECTREF a = AllocateObjectMemory(pNT);
OBJECTREF b = AllocateObjectMemory(pNT);
DoSomething (a, b);
Notice the addition of the line GCPROTECT_BEGIN(a). GCPROTECT_BEGIN is a macro whose argument is any OBJECTREF-typed storage location (it has to be an expression that can you can legally apply the address-of (&) operator to.) GCPROTECT_BEGIN tells the GC two things:
1. The GC is not to discard any object referred to by the reference stored in local “a”.
2. If the GC moves the object referred to by “a”, it must update “a” to point to the new location.
Now, if the second AllocateObjectMemory() triggers a GC, the “a” object will still be around afterwards, and the local variable “a” will still point to it. “a” may not contain the same address as before, but it will point to the same object. Hence, DoSomething() receives the correct data.
Note that we didn’t similarly protect ‘b” because the caller has no use for “b” after DoSomething() returns. Furthermore, there’s no point in keeping “b” updated because DoSomething() receives a copy of the reference (don’t confuse with “copy of the object.”), not the reference itself. If DoSomething() internally causes GC as well, it is DoSomething()’s responsibility to protect its own copies of “a” and “b”.
Having said that, no one should complain if you play it safe and GCPROTECT “b” as well. You never know when someone might add code later that makes the protection necessary.
Every GCPROTECT_BEGIN must have a matching GCPROTECT_END, which terminates “a”’s protected status. As an additional safeguard, GCPROTECT_END overwrites “a” with garbage so that any attempt to use “a” afterward will fault. GCPROTECT_BEGIN introduces a new C scoping level that GCPROTECT_END closes, so if you use one without the other, you’ll probably experience severe build errors.
Don’t do nonlocal returns from within GCPROTECT blocks.
Never do a “return”, “goto” or other non-local return from between a GCPROTECT_BEGIN/END pair. This will leave the thread’s frame chain corrupted.
One exception: it is explicitly allowed to leave a GCPROTECT block by throwing a managed exception (usually via the COMPlusThrow() function.) The exception subsystem knows about GCPROTECT and correctly fixes up the frame chain as it unwinds.