# Pointers

# Pointers for array access

This example demonstrates how pointers can be used for C-like access to C# arrays.

unsafe
{
    var buffer = new int[1024];
    fixed (int* p = &buffer[0])
    {
        for (var i = 0; i < buffer.Length; i++)
        {
            *(p + i) = i;
        }
    }
}

The unsafe keyword is required because pointer access will not emit any bounds checks that are normally emitted when accessing C# arrays the regular way.

The fixed keyword tells the C# compiler to emit instructions to pin the object in an exception-safe way. Pinning is required to ensure that the garbage collector will not move the array in memory, as that would invalidate any pointers pointing within the array.

# Pointer arithmetic

Addition and subtraction in pointers works differently from integers. When a pointer is incremented or decremented, the address it points to is increased or decreased by the size of the referent type.

For example, the type int (alias for System.Int32) has a size of 4. If an int can be stored in address 0, the subsequent int can be stored in address 4, and so on. In code:

var ptr = (int*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr)); // prints 0
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 4
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 8

Similarly, the type long (alias for System.Int64) has a size of 8. If a long can be stored in address 0, the subsequent longcan be stored in address 8, and so on. In code:

var ptr = (long*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr)); // prints 0
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 8
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 16

The type void is special and void pointers are also special and they are used as catch-all pointers when the type isn't known or doesn't matter. Due to their size-agnostic nature, void pointers cannot be incremented or decremented:

var ptr = (void*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr));
ptr++; // compile-time error
Console.WriteLine(new IntPtr(ptr));
ptr++; // compile-time error
Console.WriteLine(new IntPtr(ptr));

# The asterisk is part of the type

In C and C++, the asterisk in the declaration of a pointer variable is part of the expression being declared. In C#, the asterisk in the declaration is part of the type.

In C, C++ and C#, the following snippet declares an int pointer:

int* a;

In C and C++, the following snippet declares an int pointer and an int variable. In C#, it declares two int pointers:

int* a, b; 

In C and C++, the following snippet declares two int pointers. In C#, it is invalid:

int *a, *b;

# void*

C# inherits from C and C++ the usage of void* as a type-agnostic and size-agnostic pointer.

void* ptr;

Any pointer type can be assigned to void* using an implicit conversion:

int* p1 = (int*)IntPtr.Zero;
void* ptr = p1;

The reverse requires an explicit conversion:

int* p1 = (int*)IntPtr.Zero;
void* ptr = p1;
int* p2 = (int*)ptr;

# Member access using ->

C# inherits from C and C++ the usage of the symbol -> as a means of accessing the members of an instance through a typed pointer.

Consider the following struct:

struct Vector2
{
    public int X;
    public int Y;
}

This is an example of the usage of -> to access its members:

Vector2 v;
v.X = 5;
v.Y = 10;

Vector2* ptr = &v;
int x = ptr->X;
int y = ptr->Y;
string s = ptr->ToString();

Console.WriteLine(x); // prints 5
Console.WriteLine(y); // prints 10
Console.WriteLine(s); // prints Vector2

# Generic pointers

The criteria that a type must satisfy in order to support pointers (see Remarks) cannot be expressed in terms of generic constraints. Therefore, any attempt to declare a pointer to a type provided through a generic type parameter will fail.

void P<T>(T obj) 
    where T : struct
{
    T* ptr = &obj; // compile-time error
}

# Remarks

# Pointers and unsafe

Due to their nature, pointers produce unverifiable code. Thus, usage of any pointer type requires an unsafe context.

The type System.IntPtr is a safe wrapper around a void*. It is intended as a more convenient alternative to void* when an unsafe context isn't otherwise required to perform the task at hand.

# Undefined behavior

Like in C and C++, incorrect usage of pointers can invoke undefined behavior, with possible side-effects being memory corruption and execution of unintended code. Due to the unverifiable nature of most pointer operations, correct usage of pointers is entirely a responsibility of the programmer.

# Types that support pointers

Unlike C and C++, not all C# types have corresponding pointer types. A type T may have a corresponding pointer type if both of the following criteria apply:

  • T is a struct type or a pointer type.
  • T contains only members that satisfy both of these criteria recursively.