Pointers And Unsafe Code In C# - Everything You Need To Know

What is a Pointer?

“A pointer is a variable that holds the memory address of another variable”.

In very short term, we can say that a “pointer is a memory address”. The address, that a pointer variable holds, directly points to the value stored in memory and that value is assigned to another variable which is not a pointer. That’s why we call them pointers (they point).
address
  
What is an Unsafe code in C#?

In C#, an unsafe code is a block of code that does not run under the control of CLR. Unsafe code actually is a block of code that includes pointers. So, in C#,  pointers are unsafe.

Before we start discussing why pointers are unsafe, you should be aware of some common terms.  CLR, Managed Code and Unmanaged Code.

What is CLR?

CLR is common language runtime that controls the execution of .net programs from loading into memory to exception handling and type safety. CLR also provides a Debug Engine, Garbage Collector, Thread Management and many other features for handling the execution of C# programs and other .NET based programming languages.

When you install .NET Framework on your computer, you basically install two main components of .NET. 

  • BCL (.NET Framework Base Class Library)
  • CLR (Common Language Runtime)
BCL is standard .NET library. You can access BCL through Windows based console apps, Windows Winform and WPF apps, ASP.NET Web apps, ASP.NET web services and Windows services. .NET framework also contains two more components.
  • CTS (Common Type Specification) which defines the data types that can be used in C# and other .NET languages.

  • CLS (Common Language Specification) which is part of CTS and it defines the rules that must be obeyed by all language compilers.
Discussing all components and features of .NET Framework is not the part of this article but as we are learning pointers and unsafe code we should be aware of these terms.

There are two more concepts that you must be aware of before discussing pointers and these are Managed Code and Unmanaged Code.

Managed Code
Managed code runs under the control of CLR. Managed code is trackable and can be garbage collected. Managed code runs by CLR under managed environment and have access to all CLR features, CLR verifies the security and type safety of managed code.

Unmanaged Code
Unmanaged code cannot be executed by CLR it is executed directly by computer CPU and have no access to CLR features. As CLR has no authorities on unmanaged code, so CLR cannot verifies the security and type safety of unmanaged code.

Why Should I learn?

Well that’s very important question, after all why should we learn pointers?

It’s because you need to use them and when you are going to use them that’s another important question.

You are going to use pointers when performance is at first priority. The pointers run in unmanaged environment where all the advantages of managed environment are removed.
  • Pointers come with many benefits like there is no time waste in unnecessary runtime checks for type safety and other correctness.
  • Pointers also used to build data structures like linked list, stack, queue etc.
Discussing and explaining the practical applications of pointers is not in scope of this article but at minimum you have reasons to continue from here.

Let’s Learn

Now, we have very good reasons to learn pointers so let’s dive into experimenting and writing some operations involved pointers.

Unsafe Context

In C#, we cannot directly declare and use pointers and the reasons we have discussed above. We need a specific code block for defining and using pointers. Using “unsafe” keyword, we can define a specific code block for writing unsafe code. Unsafe keyword denotes an unsafe context where we can define and use pointers. You can use “unsafe” modifier to declare a class or any member of it to make entire class or member considered as unsafe.

unsafe

/unsafe compiler option
The /unsafe is compiler option that allows C# compiler to compile unsafe code block, unsafe types and unsafe members. If you are unable to compile unsafe code then you have to manually check allow unsafe code option from project properties page.
  • Go to Projects property page (Project ->Properties).

    unsafe

  • Navigate to build tab

    build

  • Select Allow Unsafe Checkbox


That’s all you have to do.

Unsafe Code Properties
  • Unsafe modifier can be used at class and a method in order to make them unsafe.
  • Unsafe modifier can be used to define an unsafe code block.
  • CLR cannot verifies the security of Unsafe code so it can cause some security risks
  • Unsafe code can improve performance by avoiding runtime checks, array bounds and other CLR advantages.
Declaring Pointers

Pointers can be declared in an unsafe context by specifying asterisk (*) sign with type specification. The asterisk (*) sign called pointer indirection operator that is used to get the actual content from location pointed by pointer variable.
Declaring Pointers

The asterisk (*) also called de-reference operator and it can also be specified with variable name but it’s a good practice to keep it with type name. When you declare multiple pointers in one declaration then you have to specify the asterisk (*) with underlying type only.
Declaring Pointers

Types of Pointers

In unsafe context, a variable type can be of reference type, value type and pointer type but pointers can only point to
  • primitive value types
  • struct containing value types only 
  • other pointer types
In C#, pointers cannot point to reference types it’s because reference types managed by CLR and can be garbage collected but on other hand pointers run under unmanaged environment and cannot be tracked by GC (garbage collector), a reference can be garbage collected any time when a pointer pointing to it, so in C# pointers cannot point to,
  • reference types
  • struct containing reference types
  1. internal unsafe class Program {  
  2.     private static void Main() {  
  3.         unsafe {  
  4.             int a = 20;  
  5.   
  6.             int * ptr = & a;  
  7.   
  8.             int * ptrptr2 = ptr;  
  9.             Console.WriteLine($ "Value of a is {*ptr}");  
  10.             Console.WriteLine($ "Address of a is {(int)ptr2}");  
  11.         }  
  12.     }  
  13. }  
Result

Result

Result

Obtaining Value

You can use pointer indirection operator (*) to obtain the value at location pointed by pointer.
Paste the following code in main method of your console app.
  1. Unsafe {  
  2.     int a = 23;  
  3.     int * ptr = & a;  
  4.     Console.WriteLine($ "Value of ptr {*ptr}");  
  5. }  
Result

Result
you cannot use pointer indirection operator on void pointer to obtain value.

Result

Obtaining Address

The address of variable can be obtained by ampersand (&) sign. The ampersand (&) sign also called address-of operator.

Make sure variable is initialized before you obtain the address, compiler will not show an error if variable is not initialized and you will receive garbage value at runtime.
  1. unsafe  
  2. {  
  3. int a = 30;  
  4. int* ptr = &a;  
  5. Console.WriteLine($"Address of a is {(int)ptr}");  
  6. }  
Result



Note- You cannot obtain address of a value directly and also of a constant variable.


Structs and Pointers

In C# pointers can also be used to point to Structs only if struct contains primitive value types. If a struct contains any reference type like string or any type derived from object type, then you can’t use a pointer to point that specific struct.

The members of struct declared in unsafe context can be accessed both by member access operator (→) and de-reference operator (*).

Structs
  1. struct Rectangle {  
  2.     public int Width {  
  3.         get;  
  4.         set;  
  5.     }  
  6.     public int Height {  
  7.         get;  
  8.         set;  
  9.     }  
  10. }  
  11. internal unsafe class Program {  
  12.     private static void Main() {  
  13.         Rectangle std;  
  14.         unsafe {  
  15.             Rectangle * stdPtr = & std;  
  16.             stdPtr - > Width = 35;  
  17.             ( * stdPtr).Height = 23;  
  18.             Console.WriteLine($ "Width of Rectangle : {stdPtr->Width}");  
  19.             Console.WriteLine($ "Height of Rectangle: {(*stdPtr).Height}");  
  20.         }  
  21.     }  
Result

Result

Pointer to Arrays

We can also define pointers to access array elements from memory using pointer element access arr[index]. The index can be any int, uint, long ro ulong. Array element can also be accessed using *(arr+index) expression.

Arrays
  1. internal unsafe class Program {  
  2.     private static void Main() {  
  3.         unsafe {  
  4.             int * arr = stackalloc int[3];  
  5.             arr[0] = 97;  
  6.             arr[1] = 98;  
  7.             arr[2] = 99;  
  8.             Console.WriteLine("\t Values");  
  9.             for (int i = 0; i <= 2; i++) {  
  10.                 Console.WriteLine($ "Value at {i}: {arr[i]}");  
  11.             }  
  12.             Console.WriteLine("\n\tChars");  
  13.             for (int i = 0; i <= 2; i++) {  
  14.                 Console.WriteLine($ "Char at {i}: {(char)*(arr + i)}");  
  15.             }  
  16.   
  17.         }  
  18.   
  19.     }  
  20. }  
Result

internal unsafe class Program {     private static void Main() {         unsafe {             int * arr = stackalloc int[3];             arr[0] = 97;             arr[1] = 98;             arr[2] = 99;             Console.WriteLine("\t Values");             for (int i = 0; i <= 2; i++) {                 Console.WriteLine($ "Value at {i}: {arr[i]}");             }             Console.WriteLine("\n\tChars");             for (int i = 0; i <= 2; i++) {                 Console.WriteLine($ "Char at {i}: {(char)*(arr + i)}");             }          }      } }

Controlling Pointers

So, here comes the fun part, in this part we are going to do more cool stuff with pointers. If you are defining your pointers, then for sure somewhere you need to manipulating them.

Now, it’s time to learn increment, decrement, add, subtract, and compare pointers.

Increment and Decrement

To perform increment or decrements on a pointer we use increment operator (++) and decrement operator (--). Void* pointers cannot be incremented are decremented because they do not have any underlying type but increment and decrement on pointers depends on size of underlying type, now let me explain you that part of puzzle.


Decrementing

Question - Why address incremented by 4 of pointer to int?

Answer - The address is incremented by 4 instead of 1 it’s because in C# int is a keyword that represents underlying .Net type System.Int32 which is of size 32 bits. As 1byte = 8 bits so
32 bits = 4 bytes, according to that int is of size 4 bytes.

As I told earlier the increment and decrement on pointers depends on sizeof underlying type, by means of that pointer to int will be incremented and decremented by 4 instead of 1.
Example of int: paste the following code inside of main method of your class
  1. unsafe {  
  2.     int a = 10; //value10;  
  3.     a++; //value11;  
  4.     Console.WriteLine("\n\tBefore Increment");  
  5.   
  6.     int * p = & a;  
  7.   
  8.     Console.WriteLine($ "Value of p is: {*p} \t Address of p is:{(int)p}");  
  9.     ++p;  
  10.     Console.WriteLine("\n\tAfter Increment");  
  11.     Console.WriteLine($ "Value of p is: {*p} \t Address of p is:{(int)p}\n");  
  12.     Console.WriteLine("\tCalculation");  
  13.     Console.WriteLine($ "Size of int {sizeof(int)}");  
  14.     Console.WriteLine($ "Actual address of 11 is: {(int)p - (int)(sizeof(int))} ");  
  15. }  
Result



If you want to increment or decrement actual value instead of address, then you have to specify de-reference operator to do so.
Result

Adding to Pointers
We can add value to any type of pointer except void* pointers. Value can be of type int, uint, long, ulong. When you add a value to pointer like p+=2 then address of pointer changed. The change in address depends on type of pointer just

Suppose you have a pointer ‘p’ and you want to add ‘a’ value n into it, the change in address of pointer will result by adding n*sizeof(int).

Adding to Pointers

Paste the following code in main method of your console app.
  1. unsafe {  
  2.     int local = 40; //local int var  
  3.     int * p = & local; //pointer to int  
  4.     int n = 2; //value to add in pointer  
  5.     int addressBefore = (int) p;  
  6.     Console.WriteLine($ "Address of p before adding 2:\t {addressBefore}");  
  7.     p += n; //adding value to pointer//  
  8.     int addressAfter = (int) p;  
  9.     Console.WriteLine($ "Address of p after adding 2:\t {addressAfter}\n");  
  10.     Console.WriteLine($ "Difference b/w addresses:\t {addressAfter - addressBefore}");  
  11.     Console.WriteLine($ "2*sizeof(int):\t\t\t {2 * sizeof(int)}");  
  12. }  
Result

Result

Subtracting from Pointers

Subtracting from pointers is same as adding but here we will be subtracting things instead of adding. Any type of value from int, uint, long, ulong can be subtracted from any type of pointer except void* pointers. When you subtract a value to pointer like p -=2 then change in address of pointer will result by subtracting n*sizeof(int).

Example - Suppose we have to pointers and want to calculate the difference b/w values and addresses.
  1. unsafe {  
  2.     int local = 40; //local int var  
  3.     int * p = & local; //pointer to int  
  4.     int local2 = 50; //local int var  
  5.     int * p2 = & local2; //pointer to int  
  6.     long difference = p - p2;  
  7.     Console.WriteLine($ "Address of p: {(int)p}");  
  8.     Console.WriteLine($ "Address of p2: {(int)p2}");  
  9.     Console.WriteLine($ "Difference b/w value: {*p2-*p}");  
  10.     Console.WriteLine($ "Difference b/w addresses: {difference}");  
  11. }  
Example

Pointer Comparison

Comparison of pointers is very simple and straightforward. As pointers contain addresses and by using de-reference (*) operator we can also get actual content at that address as well so we can compare both addresses and values by using following comparison operators.

== != < > <= >=

Example:
Here we have to pointers p and p2 and different comparisons b/w addresses and values. Paste the following code in main method of your console app.

  1. unsafe {  
  2.     int local = 40; //local int var  
  3.     int * p = & local; //pointer to int  
  4.     int local2 = 50; //local2 int var  
  5.     int * p2 = & local2; //pointer to int  
  6.     Console.WriteLine($ "p==p2: \t\t{p == p2}");  
  7.     Console.WriteLine($ "p!=p2: \t\t{p != p2}");  
  8.     Console.WriteLine($ "*p==*p2: \t{*p == *p2}");  
  9.     Console.WriteLine($ "*p>*p2: \t{*p > *p2}");  
  10.     Console.WriteLine($ "*p<*p2: \t{*p < *p2}");  
  11. }  
Result

Result

Up Next
    Ebook Download
    View all
    Learn
    View all