Working with Pointer Types


There are value types, reference types and pointer types.


To work with pointer types, you get specific operators and keywords that allow you to bypass the .NET Runtime's memory management scheme and take matters into your own hands.


  1. * : This operator is used to create a pointer variable (i.e. a variable that represents a direct location in memory).
  2. & : This operator is used to obtain the address of a variable in a memory.
  3. -> : This operator is used to access fields of a type that is represented by a pointer (the unsafe version of the C# dot operator).
  4. [] : This operator (in an unsafe context) allows you to index the slot pointed to by a pointer variable.
  5. ++,-- : In an unsafe context, the increment and decrement operators can be applied to pointer-types.
  6. +,- : In an unsafe context, addition and subtraction can be applied to pointer-types.
  7. ==,!=,>,<,<=,>= : In an unsafe context, the comparison operators can be application to pointer-types.
  8. stackalloc: In an unsafe context, this keyword can be used to allocate C# arrays directly on the stack.
  9. fixed : In an unsafe context, this keyword can be used to allocate to fix a variable so that its address can be found.


When you decide to make use of this C# language feature, you are required to inform the C# compiler of your intentions by enabling your project to support "unsafe code".


<PropertyGroup>
        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
         ...
</PropertyGroup>


The unsafe keyword

When you want to work with Pointers in C#, you must specifically declare a block of "unsafe code" using the "unsafe" keyword, (any code that is not marked with the unsafe keyword is considered "safe" automatically).


unsafe
 {
     // Work with pointer types here!
 }
// can' work with pointers here
 

In addition to declaring a scope of unsafe code within a method, you can build structures,classes, type members and parameters that are "unsafe".


unsafe struct Node // This entire structure is "unsafe"
 {
     public int Value;
     public Node* Left;
     public Node* Right;
 }
 
public struct Node2 // This struct is safe,but the Node2* members are not.
 {
     public int Value; // Technically, you may access "Value" from outside an unsafe context, but not "Left" and "Right".
     public unsafe Node2* Left;
     public unsafe Node2* Right;
 }


Methods (static or instance level) may be marked as unsafe as well.


static unsafe void SquareIntPointer(int* myIntPointer)
 {
     // square the value just for a test.
     *myIntPointer *= *myIntPointer;
 }
 
unsafe
 {
     int myInt = 10;
     // OK! because we are in an unsafe context.
     SquareIntPointer(&myInt);
     Console.WriteLine(myInt);
 }
 
int myInt2 = 5;
// Compiler Error! Must be in unsafe context!
SquareIntPointer(&myInt2);
Console.WriteLine(myInt2);


If you would rather not force the caller to wrap the invocation within an unsafe context, you could wrap all the top-level statements with an unsafe block. If you are using a Main() method as entry point, you can update Main() with the unsafe keyword.


static unsafe void Main(string[] args)
 {
     int myInt2 = 5;
     SquareIntPointer(&myInt2);
 }


Working with the * and & Operators

You are free to build pointers to data types using the * operator and obtain the address of what is being pointed to using the & operator.


// NO! This is incorrect under C#
int *pi,*pj;
// Yes! This is the way of C#
int* pi, pj;
 
static unsafe void PrintValueAndAddress()
 {
     int myInt;
     int* ptrToMyInt = &myInt; // Define an int pointer, and assign it the address of myInt.
     *ptrToMyInt = 123; // Assign value of myInt using pointer indirection.
     Console.WriteLine(myInt);
     Console.WriteLine((int)&ptrToMyInt);
 }


An Unsafe (and Safe) Swap Function


unsafe static void UnsafeSwap(int* i,int* j)
 {
     int temp = *i;
     *i = *j;
     *j = temp;
 }
 
static void SafeSwap(ref int i,ref int j)
 {
     int temp = i;
     i = j;
     j = temp;
 }
 
int i = 10, j = 20;
SafeSwap(ref i, ref j);
unsafe
 {
     UnsafeSwap(&i, &j);
 }


Field Access via Pointers (the -> Operator)


struct Point
 {
     public int X;
     public int Y;
     public override string ToString()
     {
         return X + " " + Y;
     }
 }
 
 
static unsafe void UsePointerToPoint()
 {
     // Access members via pointer
     Point point;
     Point* p = &point;
     p->X = 100;
     p->Y = 200;
     Console.WriteLine(p->ToString());
     // Access members via pointer indirection.
     Point point2;
     Point* p2 = &point2;
     (*p2).X = 100;
     (*p2).Y = 200;
     Console.WriteLine((*p2).ToString());
 }



The stackalloc Keyword

In an unsafe context, you may need to declare a local variable that allocates memory directly from the call stack. To do so, C# provides the "stackalloc" keyword.


static unsafe string UnsafeStackAlloc()
 {
     char* p = stackalloc char[52];
     for (int k = 0; k < 52; k++)
     {
         p[k] = (char)(k + 65);
     }
     return new string(p);
 }


Pinning a Type via the fixed Keyword

In the previous ex; we used a value type named Point(struct). Let's use complex a reference type;


class PointRef
 {
     public int X;
     public int Y;
     public override string ToString()
     {
         return X + " " + Y;
     }
 }


If the caller declares a variable of type Point, the memory is allocated on the garbage collected heap. The burning question then becomes "what is an unsafe context wants to interact with this object ( or any object on the heap ).".

Given that garbage collection can occur at any moment, imagine the problems encountered when accessing the members of Point at the very point in time a sweep of the heap is underway. Theoretically, it is possible that the unsafe context is attempting to interact with a member that is no longer access be or has been repositioned on the heap after surviving a general sweep.


To lack a reference type variable in memory from an unsafe context, C# provides "fixed" keyword. The fixed statement sets a pointed to a managed type and "pins" that variable during the execution of the code. Without "fixed", pointers to managed variables would be of little use, since gc could relocate the variable unpredictably. (In fact, the C# compiler will not allow you to set a pointer to a managed variable except in a "fixed" statement).


unsafe static void UseAndPinPoint()
 {
     PointRef pt = new PointRef
     {
         X = 5,
         Y = 9
     };
 }
 
// Pin pt in place so it will not be moved or GE-ed
fixed(int* p = &pt.X)
 {
     // Use int* variable here;
 }
// pt is now unpinned, and reaty to be GC-ed once the method completed.
Console.WriteLine(pt);


The sizeof Keyword

The C# sizeof keyword is used to obtain the size in bytes of an intrinsic data type, but not a custom type, unless within an unsafe context.


static void UseSizeOfOperator()
 {
     Console.WriteLine(sizeof(short));
     Console.WriteLine(sizeof(int));
 }


However, if you want to get size of your custom Point structure, you need to update this method as so ("unsafe" keyword has been added).

...
unsafe
 {
     Console.WriteLine(sizeof(Point));
 }