Understanding Unsafe Code in C#

This article helps us to understand and use unsafe code in C# programming.

Before we start any discussion of Unsafe Code let us see an example.

  1. static void Main(string[] args)  
  2.  {  
  3.      int ab = 32;  
  4.      int* p = &ab;  
  5.      Console.WriteLine("value of ab is {0}", *p);  
  6.      Console.ReadLine();              
  7.  }  

When I compile this code I encounter the following errors:



Why these errors occur in the program

These errors occur because we executed our program in Safe mode but we must execute our program in Unsafe mode if we are using pointers.

Now a question is, “What is meant by Safe and Unsafe Code?”

First we understand what Managed and Unmanaged Code are.

Managed Code

Managed code is that code that executes under the supervision of the CLR. The CLR is responsible for various housekeeping tasks, like:

  • Managing memory for the objects
  • Performing type verification
  • Doing garbage collection

Unmanaged Code

On the other hand, unmanaged code is code that executes outside the context of the CLR. The best example of this is our traditional Win32 DLLs like kernel32.dll and user32.dll.

In unmanaged code a programmer is responsible for:

  • Calling the memory allocation function
  • Making sure that the casting is done right
  • Making sure that the memory is released when the work is done

Now to understand what UnsafeMode is.

Definition of Unsafe Mode

Unsafe is a C# programming language keyword to denote a section of code that is not managed by the Common Language Runtime (CLR) of the .NET Framework, or unmanaged code. Unsafe is used in the declaration of a type or member or to specify a block code. When used to specify a method, the context of the entire method is unsafe.

To maintain type safety and security, C# does not support pointer arithmetic, by default. However, using the unsafe keyword, you can define an unsafe context in which pointers can be used. For more information about pointers, see the topic Pointer types.

Unsafe code can create issues with stability and security, due to its inherent complex syntax and potential for memory related errors, such as stack overflow, accessing and overwriting system memory. Extra developer care is paramount for averting potential errors or security risks.

Example

The following is an example of Unsafe Mode:

  1. unsafe static void Main()  
  2. {  
  3.    fixed (char* value = "safe")  
  4.    {  
  5.       char* ptr = value;  
  6.       while (*ptr != '\0')  
  7.       {  
  8.          Console.WriteLine(*ptr);  
  9.          ++ptr;  
  10.       }  
  11.    }  
  12. }  
Output

s
a
f
e

Writing unsafe code requires the use of the two special keywords unsafe and fixed. Now to understand the meaning of both of those keywords.

  • Unsafe: the Unsafe keyword tells the compiler that this code will run in unsafe mode.

  • Fixed: We use fixed buffers inside an unsafe context. With a fixed buffer, you can write and read raw memory without the managed overhead. Enter the fixed keyword. When used for a block of statements, it tells the CLR that the object in question cannot be relocated.

How to run a program in unsafe mode

  1. First go to the View tab.
  2. Select the Solution Explorer option.
  3. Expand the Solution Explorer a double-click on the Property option.
  4. Now select the option of ”Allow unsafe code” and mark it Check.

Let us see some examples and understand how to use it .

Example 1

Retrieving the Data Value Using a Pointer

You can retrieve the data stored at the location referenced by the pointer variable, using the ToString()method.

The following example shows this:

  1. static unsafe void Main(string[] args)  
  2.  {  
  3.     int var = 20;  
  4.     int* p = &var;  
  5.     Console.WriteLine("Data is: {0} ", var);  
  6.     Console.WriteLine("Data is: {0} ", p->ToString());  
  7.     Console.WriteLine("Address is: {0} ", (int)p);  
  8.     Console.ReadKey();      
  9.  }  

Output



Example 2

Instead using the unsafe keyword for the Main function we can use it for a specific block of code, such as:

  1. static void Main(string[] args)  
  2. {  
  3.    Unsafe  
  4.    {  
  5.        int var = 20;  
  6.        int* p = &var;  
  7.        Console.WriteLine("Data is: {0} ", var);  
  8.        Console.WriteLine("Data is: {0} ", p->ToString());  
  9.        Console.WriteLine("Address is: {0} ", (int)p);  
  10.        Console.ReadKey();  
  11.    }                
  12. }  
Output

The output will remain the same as above.

Example 3

In this program a function with the unsafe modifier is called from a normal function. This program shows that a managed code can call unmanaged functions.

  1. class Program  
  2. {  
  3.    static unsafe void Main(string[] args)  
  4.    {   
  5.        Unsafe();  
  6.        Console.ReadKey();        
  7.    }  
  8.    public static unsafe void Unsafe()  
  9.    {  
  10.        int var = 20;  
  11.        int* p = &var;  
  12.        Console.WriteLine("Data is: {0} ", var);  
  13.        Console.WriteLine("Data is: {0} ", p->ToString());  
  14.        Console.WriteLine("Address is: {0} ", (int)p);  
  15.    }  
  16. }  
Output

The output will the remain same as above.

Example 4

You can pass a pointer variable to a method as a parameter.

 

  1. static unsafe void Main(string[] args)  
  2.  {  
  3.      Program p = new Program();  
  4.      int var1 = 10;  
  5.      int var2 = 20;  
  6.      int* x = &var1;  
  7.      int* y = &var2;  
  8.      Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2);  
  9.      p.swap(x, y);  
  10.      Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2);  
  11.      Console.ReadKey();    
  12.  }  
  13.   
  14.  public unsafe void swap(int* p, int* q)  
  15.  {  
  16.      int temp = *p;  
  17.      *p = *q;  
  18.      *q = temp;  
  19.  }  
Output



Example 5

In C#, an array name and a pointer to a data type with the same array data are not the same variable type. For example,  

int *p and int[] p, are not the same type. You can increment the pointer variable p because it is not fixed in memory but an array address is fixed in memory and you can't increment that.

  1. {  
  2.     int[] list = { 10, 20,30,40,50};  
  3.     fixed (int* ptr = list)    
  4.     for (int i = 0; i < list.Length; i++)  
  5.     {  
  6.         Console.WriteLine("Address of list[{0}]={1}", i, (int)(ptr + i));  
  7.         Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));  
  8.     }  
  9.     Console.ReadKey();  
  10. }  
Output



Example 6

  1. struct My_struct  
  2. {  
  3.     public int Value1;  
  4.     public double Value2;  
  5. }  
  6.   
  7. class Program  
  8. {  
  9.     static unsafe void Main(string[] args)  
  10.     {  
  11.         My_struct refPoint = new My_struct();  
  12.         refPoint.Value1 = 20;  
  13.         refPoint.Value2 = 30;  
  14.         My_struct* pPoint = &refPoint;  
  15.         Console.WriteLine("Value1 = " + pPoint->Value2);  
  16.         Console.WriteLine("Value2 = " + pPoint->Value2);  
  17.         Console.WriteLine("value1 = " + (*pPoint).Value1);  
  18.         Console.WriteLine("value2 = " + (*pPoint).Value2);  
  19.         Console.ReadKey();  
  20.     }
  21. }  
Output



Now we consider some points about Unsafe Mode.

Advantages of UnSafe Mode

  1. It increase the performance of the Program.
  2. We use fixed buffers inside an unsafe context. With a fixed buffer, you can write and read raw memory without any of the managed overhead.
  3. It provides a way to interface with memory.

Disadvantages of Unsafe Mode

  1. It increase the responsibility of the programmer to check for security issues and extra developer care is paramount to averting potential errors or security risks.
  2. It bypasses security. Because the CLR maintains type safety and security, C# does not support pointer arithmetic in managed code, unlike C/C++. The unsafe keyword allows pointer usage in unmanaged code. However, safety is not guaranteed because strict object access rules are not followed.
  3. It also avoids type checking, that can generate errors sometimes.

When to use Unsafe Mode

  1. If we are using pointers.
  2. When we are writing code that interfaces with the operating system or other unmanaged code.
  3. If we want to implement a time-critical algorithm or want to access a memory-mapped device.
Reference: Techopedia