Author avatar

Dániel Szabó

Safety First!

Dániel Szabó

  • Aug 15, 2019
  • 7 Min read
  • 31 Views
  • Aug 15, 2019
  • 7 Min read
  • 31 Views
Languages Frameworks and Tools
C#

Introduction

In this guide, we’ll be exploring unsafe code. When we talk about unsafe code in C#, we’re talking about when we run code without the supervision of the CLR - short for Common Language Runtime. There are pointers in C# that you can use to determine if code is unsafe.= It is not an exact science but we are here to shed some light on this mystery.

In order to maintain security and type safety, C# does not support pointer arithmetic by default. However, the unsafe keyword can override that.

What is CLR?

The Common Language Runtime controls the execution of .NET programs, from loading to memory to exception handling and type safety. There are numerous facilities provided by the CLR. There are two main facilities provided by the CLR. The first is the Garbage Collector and the second is Thread Management. Garbage Collection is explained in detail in my guide on Destructors.

There are two main components of the .NET framework:

  1. BCL (Base Class Library)
  2. CLR (Common Language Runtime)

The BCL is nothing more than the standard .NET library, which might be accessed through console apps, WPF apps, ASP.NET apps, etc. It’s a set of predefined classes and functionalities that you can reuse. This allows you to build your application faster and make them portable across different machines that share a compatible .NET version. It also reduces the size of your applications.

BCL can be broken down into two more parts:

  1. CTS (Common Type Specification)
  2. CLS (Common language specification)

CTS defines the supported data types which you can use in C# and .NET.

CLS defines a set of rules which language compilers need to obey.

We need to clarify two more concepts that are necessary to understand what unsafe code, and it's execution, mean.

Two types of code.

  1. Managed Code (safe code)
  2. Unmanaged Code (a.k.a. unsafe code)

Managed code runs entirely under the supervision of the CLR. It is tracked and garbage collected automatically; it also has access to the full arsenal of facilities provided by the CLR. Unmanaged code cannot be executed by the CLR. It is used in conjunction with facilities provided by the Operating System. Resources, like files, network streams, media streams, and apps, interacting with these resources are usually unmanaged code and are considered less safe. When we are working, for example, on an application which is able to edit a video file, our application usually needs to take care of opening, editing, and closing the resources. Otherwise, it can become corrupted and the media player might refuse to handle it.

The CLR lacks authority over the unmanaged code; it is unable to provide type-check, or type-safety and security.

Enter Pointers

Pointers are only interpreted in the context of unsafe or unmanaged code. The pointer can be a pointer type, value type, or a reference type.

In order to define a pointer, we need the following code.

1
2
int* myIntPointer;
void* myVoidPointer;
csharp

The name of the type defined before the * is called referent type. Pointer types do not inherit from the object base class, and there is no conversion between pointer types and the object base class. Boxing and Unboxing do not support pointers either.

You can define multiple pointers in the same line also.

1
int* mageDamage, barbarianDamage, amazonDamage, rogueDamage;
csharp

The garbage collector does not protect the object references., This means that, even if a pointer points to a reference or a struct holding references, the garbage collector can collect it. So, it is forbidden for a pointer to point to:

  1. A reference.
  2. A struct that contains references.

You can define different types of pointers:

  1. int* p - pointer to an integer.
  2. int** p - pointer to a pointer to an integer.
  3. int*[] p - an array of pointers to integers.
  4. char* p - pointer to a character.
  5. void* p - pointer to an unknown type.

When you are using pointers, the compiler needs the -unsafe argument passed.

Let's traverse an array of odd numbers with the help of pointers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int[] a = new int[5] { 3, 5, 7, 9, 11 };
unsafe{
    fixed(int* p = &a[0]){
        int* p2 = p;
        Console.WriteLine(*p2);
        p2 += 1;
        Console.WriteLine(*p2);
        p2 += 1;
        Console.WriteLine(*p2);
        p2 += 1;
        Console.WriteLine(*p2);
        p2 += 1;
        Console.WriteLine(*p2);
        //Dereferencing
        *p += 1;
        Console.WriteLine(*p);
    }
}
csharp

The output for the code once executed should look like this.

1
2
3
4
5
6
3
5
7
9
11
4
bash

The unsafe keyword marks the section of the code for the compiler that it will pointers. The fixed keyword means we pin the *p variable to the top of our heap. The last *p += 1 line is called dereferencing. Dereferencing means we overwrite the value referenced by the pointer, and then we take the current value and increase it by one. This only works because our array holds integers, which are compatible with the operator; other types would throw an error. In order to run this code, you either need to pass the -unsafe switch or the /unsafe, depending on your compiler. If you are using Visual Studio, you need to allow the execution and build of unsafe code because, by default, it is disabled and it will throw you a nice error warning you about security.

There is a list of operators and statements which you can use to manipulate your pointers in an unsafe context.

Operators:

    • --- pointer indirector
  1. -> --- member accessor for struct
  2. [] --- indexing
  3. & --- address of a variable
  4. --, ++ --- increment and decrement
  5. -, + --- Arithmentic
  6. ==, !=, <, >, <=, >= --- Compare

Statements:

  1. stackalloc - Allocating memory on stack.
  2. fixed - Temporary fixed variable to find its address.

Unsafe code properties:

  1. Methods, types, and code blocks can be defined as unsafe
  2. Unsafe code is required for native functions which rely on pointers
  3. Unsafe code introduces security and stability risks
  4. Code that holds unsafe blocks need to be compiled with -unsafe
  5. In some cases, the unsafe code can result in better performance because the array bound checks are removed.

Conclusion

At first, you may have gotten the wrong impression about unsafe code because of its name. Now that you have completed this guide, I hope you have changed your mind and realized that the word unsafe comes from the CLR’s perspective. That means there are some problems that arise from dangling pointers and uninitialized variables which are, by default, handled by C# and the CLR. However, when you are writing applications that interface with facilities other than the CLR provided facilities, you’ll want to use pointers. The are times when your entry is considered unsafe and you need to explicitly tell the compiler/Visual Studio that "Yes I know what I'm doing, now compile my code." I hope that you have found what you were looking for.

0