0Pricing
C# Academy · Lesson

Unsafe Code, Pointers & Fixed Buffers

Use the unsafe keyword, work with pointers, pin managed memory with fixed, and access fixed-size buffers in structs.

Unsafe Code, Pointers & Fixed Buffers is a free C# Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is Unsafe Code?

C#'s unsafe keyword unlocks direct memory manipulation: raw pointers, pointer arithmetic, and fixed-size buffers. It bypasses the GC safety guarantees. Enable it with <AllowUnsafeBlocks>true</AllowUnsafeBlocks> in your project file.

// Must add to .csproj:
// <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

// Unsafe blocks can appear inside methods:
unsafe
{
    int x = 42;
    int* ptr = &x;          // get address of x
    Console.WriteLine(*ptr); // dereference — prints 42
    *ptr = 100;
    Console.WriteLine(x);    // 100 — x was mutated via pointer
}

Pointer Types in C#

Pointer syntax mirrors C/C++: T* is a pointer to T. You can declare pointers to unmanaged value types (int, double, structs with no reference fields). Pointers to managed types are not allowed.

unsafe
{
    int    i = 10;
    double d = 3.14;

    int*    ip = &i;
    double* dp = &d;

    // Dereference with *
    Console.WriteLine(*ip); // 10

    // Pointer arithmetic — move to next int in memory
    int[] arr = { 1, 2, 3 };
    fixed (int* p = arr)
    {
        Console.WriteLine(*(p + 0)); // 1
        Console.WriteLine(*(p + 1)); // 2
        Console.WriteLine(*(p + 2)); // 3
    }
}

The fixed Statement

Managed objects can be relocated by the GC. To take the address of a managed object, you must pin it with fixed. The GC won't move the object for the duration of the fixed block.

byte[] buffer = new byte[256];

unsafe
{
    fixed (byte* pBuf = buffer)
    {
        // pBuf is valid only inside this block
        // GC won't relocate 'buffer' here
        for (int i = 0; i < buffer.Length; i++)
            pBuf[i] = (byte)i;
    }
    // After 'fixed', GC can move 'buffer' again
}

Console.WriteLine(buffer[5]); // 5

Pointer Arithmetic

You can add or subtract integers from pointers. Adding 1 to a int* advances it by 4 bytes (sizeof int). This is the basis of fast bulk-memory operations.

unsafe
{
    int[] data = { 10, 20, 30, 40, 50 };
    fixed (int* start = data)
    {
        int* p = start;
        long sum = 0;
        for (int i = 0; i < data.Length; i++)
        {
            sum += *p;
            p++;    // advance by sizeof(int) = 4 bytes
        }
        Console.WriteLine(sum); // 150

        // Or with index syntax:
        Console.WriteLine(start[2]); // 30
    }
}

stackalloc: Stack Allocation

stackalloc allocates a block of memory on the stack (not the heap). No GC pressure, no pinning needed. Limited to the current method's stack frame — memory is freed automatically when the method returns.

// Stack-allocated buffer — zero heap allocation
Span<int> buffer = stackalloc int[128]; // safe Span wrapper
buffer.Fill(0);
buffer[0] = 42;
Console.WriteLine(buffer[0]); // 42

// Or raw pointer form (requires unsafe):
unsafe
{
    int* raw = stackalloc int[128];
    raw[0] = 99;
    Console.WriteLine(raw[0]); // 99
}
// Stack frame popped — memory gone

Fixed-Size Buffers in Structs

A fixed-size buffer embeds a fixed-length array inline in a struct — no heap allocation, no extra pointer. Use the fixed modifier inside a struct declared in an unsafe context.

unsafe struct NetworkPacket
{
    public int  Length;
    public byte Command;
    public fixed byte Payload[256]; // 256 bytes inline in the struct
}

unsafe
{
    NetworkPacket pkt;
    pkt.Length  = 10;
    pkt.Command = 0x01;

    // Fill payload inline — no heap allocation
    for (int i = 0; i < 10; i++)
        pkt.Payload[i] = (byte)i;

    Console.WriteLine(pkt.Payload[5]); // 5
}
// sizeof(NetworkPacket) = 4 + 1 + 256 = ~261 bytes on stack

void* and Casting

A void* is a typeless pointer — useful for generic memory operations. You must cast to a typed pointer before dereferencing. sizeof(T) operator works on unmanaged types in unsafe context.

unsafe
{
    int value = 42;
    void* vp = &value;      // typeless pointer
    int*  ip = (int*)vp;    // cast back to int*
    Console.WriteLine(*ip); // 42

    // sizeof works on unmanaged types
    Console.WriteLine(sizeof(int));    // 4
    Console.WriteLine(sizeof(double)); // 8
    Console.WriteLine(sizeof(long));   // 8
}

MemoryMarshal & Unsafe Class

System.Runtime.CompilerServices.Unsafe and System.Runtime.InteropServices.MemoryMarshal provide safe (non-keyword) alternatives for many low-level operations, compatible with Span and usable without unsafe keyword.

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// Reinterpret a Span<byte> as Span<int> — no copy, no unsafe keyword
byte[] raw = new byte[16];
Span<int> ints = MemoryMarshal.Cast<byte, int>(raw);
ints[0] = 12345678;
Console.WriteLine(ints[0]); // 12345678

// Unsafe.As<,> — reinterpret reference type (advanced)
ref int first = ref MemoryMarshal.GetReference(ints);
Unsafe.Add(ref first, 1) = 99999;
Console.WriteLine(ints[1]); // 99999

When (Not) to Use Unsafe

Use unsafe code only when there's a clear performance need and no safe alternative. Keep unsafe surface minimal — isolate it to private helper methods or dedicated types. Write exhaustive tests and document assumptions.

// GOOD: narrow unsafe scope
public static unsafe int SumBytes(ReadOnlySpan<byte> data)
{
    int total = 0;
    fixed (byte* p = data)
    {
        byte* end = p + data.Length;
        for (byte* cur = p; cur < end; cur++)
            total += *cur;
    }
    return total;
}

// BETTER for modern code: use SIMD via Vector<T> or hardware intrinsics
// which stay in managed code but still achieve native-level perf

Real-World: Parsing a Binary Protocol

Unsafe pointers and fixed-size buffers shine when parsing binary protocols where struct layout must match a wire format exactly. The struct can be cast directly from a byte buffer with zero copies.

[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct MessageHeader
{
    public uint  Magic;        // 4 bytes
    public ushort Version;    // 2 bytes
    public ushort PayloadLen; // 2 bytes
    public fixed byte Id[16]; // 16 bytes GUID inline
}

unsafe ReadOnlySpan<byte> SerializeHeader(MessageHeader hdr)
{
    // Cast struct directly to bytes — zero copy
    return new ReadOnlySpan<byte>(&hdr, sizeof(MessageHeader));
}

unsafe MessageHeader ParseHeader(ReadOnlySpan<byte> buf)
{
    fixed (byte* p = buf)
        return *(MessageHeader*)p; // reinterpret cast
}

Quick Check

Why is the fixed statement needed when taking the address of a managed object?

Recap: Unsafe Code, Pointers & Fixed Buffers

Key takeaways:

  • unsafe keyword unlocks raw pointers — requires AllowUnsafeBlocks in project
  • fixed statement pins managed objects so the GC won't relocate them
  • Pointer arithmetic advances by element size (not bytes)
  • stackalloc allocates on the stack — no GC pressure, freed on method exit
  • Fixed-size buffers (fixed byte Payload[N]) embed arrays inline in structs
  • Prefer Span<T>, MemoryMarshal, and Unsafe class for most scenarios

Frequently asked questions

Is the “Unsafe Code, Pointers & Fixed Buffers” lesson free?

Yes — the full text of “Unsafe Code, Pointers & Fixed Buffers” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “Unsafe Code, Pointers & Fixed Buffers”?

Use the unsafe keyword, work with pointers, pin managed memory with fixed, and access fixed-size buffers in structs. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Unsafe Code, Pointers & Fixed Buffers” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. P/Invoke Fundamentals
  2. LibraryImport & Source-Generated P/Invoke
  3. Unsafe Code, Pointers & Fixed Buffers
  4. COM Interop & Runtime Callable Wrappers
← Back to C# Academy