Return of the GC Hole: Another one from the C++/CLI trenches

A few days ago I wrote of a GC hole I accidently created in my C++/CLI code (with thanks to commenter Nish for the name of the phenomenon). I had the following code snippet set inside a private function:

   pin_ptr<Byte> pinnedBuffer = &data[0];
   char* buffer = (char*)pinnedBuffer;
    

Because the pin_ptr went out of scope when the function ended, I had a pointer to a memory location that was quickly moved by the GC, and got me a whole buffer full of garbage. To avoid that, I moved those two lines of code to my main method and pinned it throughout that method.

I thought that was the end of that, but today I noticed that I was still getting garbage occasionally in my buffer. This stumped me for a bit - it seemed like the pin_ptr wasn't doing its job - until I noticed how my C++ code looked:

public void DoStuff (array<Byte>^ data)

char* buffer;

if (data.Length > 0)
{
    pin_ptr<Byte> pinnedBuffer = &data[0];
    buffer = (char*)pinnedBuffer;
}

// Now do stuff

For C++ programmers this is probably obvious. For me and other C# devs, not so much. C++'s scoping rules say that when the if block terminates, the pin_ptr<Byte> goes out of scope immediately. This causes the pinned memory to be released, and allows the GC to move my array. Meanwhile my unmanaged code goes over the buffer and reads data. If I'm lucky, the GC won't start until I'm finished and all is well. If the GC IS active, I can either get garbage data (whatever is now pointed to by buffer) or, even worse, an access violation if I now point to protected memory.

No Comments