Skip to Content
Course content

167: The unsafe and fixed Keywords

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're working in a high-end art gallery. Normally, you don't touch the paintings yourself. If you want to see a piece, you ask the curator, and they bring it to you or guide you to it. The curator handles all the logistics—they might move a painting from the east wing to the west wing overnight to make room for a new exhibit, but as long as you just ask for "The Starry Night," the curator finds it for you regardless of where it actually sits in the building. You don't need to know the GPS coordinates of the canvas; you just need the name.

But let's say you're a master restoration expert. To fix a microscopic tear in the canvas, you can't just "ask" for the painting. You need to go into the vault, put the painting on a table, and put your needle exactly three millimeters from the left edge. Now, here is the danger: if the curator decides to move that painting to a different table while your needle is mid-stroke, you're going to poke a hole right through the wall. To prevent this, you have to "pin" the painting to the table, telling the curators, "Do not move this piece until I am finished."

In C#, the "curator" is the Common Language Runtime (CLR) and its Garbage Collector (GC). Usually, you deal with references, not addresses. The GC is constantly moving objects around in memory to defragment it. The unsafe keyword is you deciding to be the restoration expert, and the fixed keyword is you pinning the painting to the table.

Breaking into the Memory Vault

By default, C# is a "safe" language. This means the runtime prevents you from accessing memory addresses directly, which stops you from accidentally overwriting critical system data or crashing the app with a segmentation fault. When you mark a block of code as unsafe, you're telling the compiler, "I know what I'm doing; let me use pointers."

Pointers are essentially variables that hold the memory address of another value. I'll be honest with you: in 99% of your professional career, you won't need this. But when you're doing high-performance graphics, interfacing with old C++ DLLs, or manipulating massive buffers of raw bytes, pointers are significantly faster because they bypass the bounds-checking that C# normally does on every array access.

// Note: You must enable "Allow unsafe code" in your project properties to run this.
unsafe
{
    int myValue = 10;
    int* p = &myValue; // 'p' now holds the memory address of 'myValue'
    
    Console.WriteLine($"Value: {myValue}");
    Console.WriteLine($"Address: {(long)p:X}"); 
    Console.WriteLine($"Value via pointer: {*p}"); // Dereferencing the pointer
}

Stopping the Garbage Collector from Moving the Furniture

Now, here is where things get tricky. If you take a pointer to a managed object (like an array), you have a problem. The Garbage Collector might decide to move that array to a different part of the heap to optimize memory while your pointer is still pointing to the old location. Your pointer becomes "dangling," and the next time you use it, you're reading random garbage or crashing the program.

The fixed statement solves this. It "pins" the object in place, telling the GC: "Leave this specific object exactly where it is until this block of code finishes."

Let's look at a real-world scenario: imagine we have a buffer of bytes and we want to XOR them for a very basic encryption. Using a for loop with indexers is fine, but using pointers is the "metal" way to do it.

public unsafe void ScrambleBuffer(byte[] buffer, byte key)
{
    // We 'fix' the buffer so the GC doesn't move it while we're pointer-walking
    fixed (byte* pStart = buffer)
    {
        byte* pCurrent = pStart;
        
        for (int i = 0; i < buffer.Length; i++)
        {
            // Access the value at the current address, XOR it, and move to the next byte
            *pCurrent = (byte)(*pCurrent ^ key);
            pCurrent++; // Pointer arithmetic: move to the next memory address
        }
    } 
    // Once we exit the fixed block, the GC is free to move the buffer again.
}

Notice how pCurrent++ works. In a normal array, you'd do i++ and then access buffer[i]. With pointers, you are literally sliding across the memory chip, one byte at a time. It's elegant, it's fast, and if you mess up the math, it's catastrophic. That's why we keep it wrapped in those specific keywords.




📋 Practical Task

Building a Fast-Byte Summation Tool

Your task is to create a method that calculates the total sum of all bytes in a large array using unsafe and fixed keywords. This is a common pattern in checksum calculations where performance is critical.

Requirements:

  • Create a method public unsafe long SumBytes(byte[] data).
  • Use a fixed block to obtain a pointer to the start of the array.
  • Use a pointer (not an indexer like data[i]) to iterate through the entire array.
  • Accumulate the values into a long variable to prevent overflow.
  • Return the final sum.

Testing your code:
Initialize a byte array with 1,000,000 elements (you can fill them with random values or a constant) and verify that your pointer-based sum matches the result of a standard LINQ .Sum() call.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.