[author's edit:] Indeed this appears similar to a post from 3 months ago. Both remain valuable, however, since they show different examples, and this question, in particular, generated an important discussion containing valuable expert information in the comments section.
With ref local and ref return in C# 7, it's now possible to retain a managed pointer to the interior of a reference-type class
instance indefinitely.1. Since the interior reference could be to a ValueType
, it feels like this could perhaps introduce the possibility of the managed pointer in a sense being orphaned, should the host itself get collected.
Here's a complete and self-contained example. Discussion continues in the comments within, and below.
class outer { public int m_i; }; // outer reference type containing a value-type field
static class _
{
// the lone reference to a single global instance of 'outer' in the GC heap
static outer host_inst = new outer();
// (function here for clarity, and to prevent distraction over 'keep-alive' concerns)
static ref int GetInteriorPointer()
{
ref int pi = ref host_inst.m_i; // managed pointer to interior value-type
host_inst = null; // abandon the heap object itself
return ref pi; // but return the interior pointer
}
static _()
{
ref int pi = ref GetInteriorPointer();
// At this point, the 'outer' container instance itself is not reachable, since
// its only reference was nulled-out. Normally, when an object's last GC handle
// goes out of scope it is eligible for garbage collection.
// However, in this case we still have access to a managed pointer to a field
// inside the object.
// Is 'pi' sufficient to prevent the collection of host_inst here? <-- Question
pi++; // (possible keep-alive... for an inaccessible GC object?)
}
};
The question is pretty straightforward:
If you have a managed pointer to an interior
ValueType
field of a reference-type instance, but there are no handles to the outer instance itself, is the existence of the managed pointer sufficient for keeping the host alive?
The reason one might suspect "no" is that, from IL
it seems clear that a managed pointer is IntPtr
-sized, that is, a 32- or 64-bit value just like an object reference--but in this case without the benefit of any "object header" or other obvious metadata support.2. From this, it seems like they are too raw or "stripped-down" to efficiently participate in the GC reachability graph. So if a managed ref
represents the last remaining way to access (some interior portion of) some object, but such ref
pointers are not tracked in the GC graph, the object would be subject to collection.
So if the answer is "yes," how does this work? Are there any situations where the CLR has to recover a containing object given only a managed pointer, and if so, how would this be done and is it efficient?
If the answer is "no" and the host does happen to get collected, what is the fate of the managed pointer? Does it then point to garbage and corrupt the CLR upon use, or does it somehow handle failure more gracefully?
Note that the fix would be simple but does affect overall design: it just becomes the responsibility of the developer to make sure the program independently keeps a full-blown reference to the outer instance somewhere else, and ensures that it outlives usage the managed pointer.
[1.] Indefinitely meaning, at least as far as the code that originally issues the managed pointer is concerned, since the client can use or abandon the pointer at will, without ever having to report back.
[2.] Lacking metadata, since the IL
instructions which manipulate managed pointers require a TypeRef
to be supplied as "burned-in" to the instruction stream.
Related: Recover containing GC object from managed 'ref' interior pointer