In this article, we are going to learn about the unsafe code in C#.
In general, the code that we write in C# is safe code. It creates managed objects and doesn’t access the memory directly. On the other hand, unsafe code in C# is code that is not in direct control of the Common Language Runtime (CLR). Regardless of how the word ‘unsafe’ makes it sound, it is not inherently dangerous to write unsafe code. However, the CLR would not be able to verify the safety of the code i.e. type safety, pointer errors, and so on.
To understand unsafe code and where it comes into the picture, we’ll have to understand managed and unmanaged code in C#.
Let’s dive in.
What is Managed Code?
Managed code is code whose execution is managed by a runtime. In the case of .NET, this runtime is called Common Language Runtime. We use high-level languages like C#, F#, Visual Basic, etc. to write managed code. The respective compiler then converts this code into a binary of Intermediate Language (IL).
The CLR takes this IL code, converts it into machine code using a method called Just-In-Time compiling, and executes it. The runtime is also in charge of memory management, type safety, security considerations, etc. thus effectively managing the code.
What is Unmanaged Code?
Unmanaged code is any code written outside of the .NET framework. A program written in C/C++ is an example of unmanaged code. As the CLR has no control over unmanaged code, we’re responsible for almost everything like memory management i.e. memory allocation, as well as deallocation when the work is done, and security practices.
A program written in unmanaged code is essentially ready to load into the operating system memory and start executing.
Now that we understand the distinction between managed and unmanaged code, let’s take a look at unsafe code.
Unsafe Code
C# supports an unsafe context where we can write code whose security is unverifiable by the CLR. For example, by default, C# does not support pointer arithmetic to ensure type safety and security. However, in an unsafe context, we can use pointers.
To denote an unsafe context in C#, we use the unsafe
keyword. As the CLR can’t verify the safety of unsafe code, it only allows unsafe code execution within a fully trusted assembly.
Running Unsafe Code
Let’s create a simple program to look at the usage of the unsafe
keyword:
unsafe static void Main(string[] args) { var number = 100; int* numberPtr = &number; Console.WriteLine("The value of variable: {0}", number); Console.WriteLine("The value of variable using pointer: {0}", numberPtr->ToString()); Console.WriteLine("The address of variable : {0}", (int)numberPtr); Console.ReadLine(); }
In the above code, we have declared a pointer variable numberPtr
, denoted by int*
. Unlike a normal variable that stores a value of any type (int, char, string, etc.), it stores a memory address. We provide this memory address using the address-of operator (&). The address-of operator returns the address of the variable number
.
Here, we are trying to print the location as well as the value of the variable from the location referenced by the pointer variable.
However, this code does not compile. This is because we need to specify the AllowUnsafeBlocks compiler option in order to compile code that uses unsafe
keyword.
To do this, we need to go to project properties by selecting Project > {Project Name} Properties option. We can then enable the “Allow code that uses the ‘unsafe’ keyword to compile.” option.
As an alternative, we can mark AllowUnsafeBlocks
as true
in the csproj
file of the project:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <AllowUnsafeBlocks>True</AllowUnsafeBlocks> </PropertyGroup> </Project>
This allows our code to compile and execute to return an output:
The value of variable: 100 The value of variable using pointer: 100 The address of variable : 366470540
Usage of the unsafe Keyword
In the previous example, we learned that we can use the unsafe
keyword for the Main()
function and write unsafe code inside it. Let’s look at other usages of the unsafe
keyword.
Unsafe Block
Instead of using the unsafe
keyword for the complete Main()
function, we can also use it for a block of code:
public static void Main(string[] args) { unsafe { var number = 100; int* numberPtr = &number; Console.WriteLine("The value of variable: {0}", number); Console.WriteLine("The value of variable using pointer: {0}", numberPtr->ToString()); Console.WriteLine("The address of variable : {0}", (int)numberPtr); } Console.ReadLine(); }
The above code is similar to our previous example, with the only difference being the use of unsafe
keyword for a block of code instead of the Main()
function. The output remains the same:
The value of variable: 100 The value of variable using pointer: 100 The address of variable : -1940395892
Unsafe Member Declaration
We can also use unsafe
keyword in the declaration of a member function. The entire context of such a member function is suitable for use of unsafe code:
public static unsafe void GetTriple(int* num) { *num = *num * 3; }
We can then call this method by passing a pointer variable as the parameter:
public static unsafe void Main(string[] args) { var num = 10; int* numPtr = # Console.WriteLine("GetTriple Input: {0}", num); GetTriple(numPtr); Console.WriteLine("GetTriple Output: {0}", num); Console.ReadLine(); }
And the output:
GetTriple Input: 10 GetTriple Output: 30
Pointer to Access Array Elements
The array and a pointer to a data type with the same array data are not considered the same variable types in C#. So, char[]
and char*
are not the same types. However, we can access array elements using pointers:
public static unsafe void Main(string[] args) { var text = "Happy"; fixed (char* textPtr = text) { for (var i = 0; i < text.Length; i++) { Console.WriteLine("text[{0}] : {1}", i, *(textPtr + i)); } } Console.ReadLine(); }
In the above example, we have also used fixed
statement. It prevents a movable variable from relocation by the garbage collector. The fixed
statement can only be used in an unsafe context.
It sets the pointer to a managed variable and “pins” it on the heap to disallow it from moving unpredictably. Hence in the example, the textPtr
pointer points to the start of the array using the fixed statement.
Now, we know the various usages of the unsafe keyword. Let’s take a look at when we use unsafe code and the advantages and disadvantages associated.
Why Do We Need Unsafe Code in C#?
We use unsafe code if we need to use pointers. However, we rarely need to use pointers in C# apart from some situations that warrant their use.
The most common application is calling into native code from C# to invoke structures with pointers in them. Sometimes, in performance-critical code, using pointers may also lead to performance enhancement and the unsafe context can be used in those scenarios too.
It is essential that we avoid using unsafe code unless necessary as it may lead to various pitfalls.
As the unsafe code can be executed only from an entirely trusted context, it might prove challenging to run from say, a network not configured to be trusted. It introduces several issues like buffer overruns, code not being type-safe, fragmentation of managed heap while pinning the managed variable so that the pointer can access it, and so on.
This also has maintainability repercussions. The code becomes harder to understand and maintain for programmers not used to the unsafe approach.
Difference Between Unsafe and Unmanaged Code
Unmanaged code runs outside the context of CLR. It is not managed by the runtime and hence not visible to the garbage collector. For example, the calls to C/C++ dlls.
Whereas the unsafe code still runs under the context of CLR, it just allows us the use of pointers for direct memory access. It is code outside the verifiable subset of Common Intermediate Language (CIL).
Conclusion
In this article, we’ve learned about the unsafe code in C# and various usages of the “unsafe” keyword. We’ve also learned about when to use unsafe code in our programs, along with their advantages, and disadvantages.