Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Unsafe pointers, fixed, and pinning

โญโญโญ advancedโณ 16 min read๐Ÿ“ Lesson 232 of 265

The fixed statement, pointer arithmetic, and why pinning fights the GC (and how to stop fighting).

Unsafe pointers, fixed, and pinning

The GC moves objects during compaction. A raw pointer into a managed object would dangle after a move โ€” so fixed creates a contract: within this block, the object will not move. That is pinning, and pinning is not free:

  • Pinned objects fragment the heap (the GC must route compaction around them).
  • Long pins across Gen0/1 collections degrade every collection.
  • The danger window is exactly the fixed body โ€” which is why the rule "pin narrowly" is also a performance rule, not just a safety rule.
byte[] buffer = ...;
fixed (byte* p = buffer)          // pin: GC avoids relocating buffer
{
    NativeWrite(p, buffer.Length);   // native code reads/writes it
}   // scope ends: GC may move buffer again

Pointer arithmetic rules worth internalizing:

  • p[i] on int* advances by 4 bytes per index โ€” pointer arithmetic is element-typed, not byte-typed.
  • void* has no element size โ€” cast to a typed pointer before indexing.
  • &arr[0], &local, &struct.Field are all legal on unmanaged types.
  • stackalloc int[64] yields an int* (or Span<int>) โ€” stack memory, no pinning needed, freed on frame exit.

The modern hierarchy: spans first (buffer.AsSpan() โ€” safe, optimizes to the same code), then pointers where spans are awkward (interleaved multi-array indexing, native interop edges). Measure before assuming the pointer version is faster: tier-1 JIT usually compiles span code to identical machine code โ€” the bounds checks get eliminated by range analysis the same way your manual checks replace them.

โšก Now practice

Ready to Code
Unsafe drillsScoped pinning, struct reinterpretation, and the safety reasoning that makes unsafe code defensible.
2 challenges ยท ยท ~45 min