Writing unsafe code - pointers in C#

Summary

In this article, I will give a short description of one of the feature of C# which are pointers and so-called unsafe code. This subject is particularly close for C++ programmers. Moreover, it is a feature that we do not find in Java.

Managed code

Generally speaking when you write any program in C# you create managed code. Managed code is executed under the control of Common Language Runtime (CRL). CRL causes that programmer do not need to manage memory and take care about memory's allocation and deallocation. CLR also allows you to write what is called 'unsafe code'.

Unsafe code

Unsafe code is code which does not executed under full control of CLR. It can cause some problems therefore each use must be marked as "unsafe".

...
unsafe
{
...
// unsafe context: can use pointers here
...
}
...

Keyword 'unsafe' might be also used in other cases for example we can mark class or method as unsafe: 

unsafe class Class1 {}
static unsafe void FastMove ( int* pi, int* pdi, int length) {...} 

Necessity of using the 'unsafe' keyword protects programmers before its accidental usage. If code is unsafe you might ask yourself why somebody want to use it. The answer is that from time to time, in some cases, it is necessary to use pointers.

Pointers

Pointers are special kind of variables which hold addresses other variables. If you assign address first variable to second variable you can say that first variable 'point at' the second one. There are three kinds of pointers supported by the CRL: managed pointers, unmanaged pointers and unmanaged function pointers. Managed pointers are references to managed blocks of memory which are stored on the heap. An unmanaged pointers are traditional C++ pointers and each use must be placed in unsafe block of code. An unmanaged function pointers are also the traditional C++ pointers that refer to the addresses of the function (delegates may be treat as unmanaged function pointers).

To create a pointer you can use the following declaration:

type* variable_name; 

As a type may be used each type that is not a reference-type and does not contain reference-type field. It can be only: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool any enum-type and other pointer-type. Any user-defined struct-type that contains fields of unmanaged-types can be also used.

The following examples show different pointers declarations:

int* pi //declaration a pointer to integer variable
float* pf, pq // two pointers to float variables. Not *pf, *pq
char* pz // pointer to char

As mentioned earlier the unnamed code is not verifiable by CRL and to compile it you must specify /unsafe compiler option. If you use Microsoft Visual Studio you should set to true 'Allow unsafe code block' option in project property window.

Project property window.

Basic use of pointers

There is also other operator which is inseparably linked with pointers. It is & operator. The & operator returns the memory address of its operand.

For example:

unsafe
{
int* pi;
int x = 1;
pi = &x;
System.Console.WriteLine("Value of x is: " + *pi);
}

In this example we created two variables, 'pi' as pointer to int and 'x' as int. Then we put into 'pi' the memory address of the variable 'x'. It is very important to understand that in 'pi' variable we put address to 'x', not 'x' value (using: pi = x returns error "Cannot implicitly convert type 'int' to 'int*'")

After compilation and running the output will be:

Value of x is: 1 

Pointers accept null values, there is also possible to use pointers which type is void.

The following code will be compile correctly.

unsafe
{
nt x = 10;
void* px = &x;
double *pd = (double*)px;
}

Keyword fixed and garbage collector

Using pointers in C# require much more attention then in C++. That is because of garbage collector (g.c.) which can run memory cleaning. During cleaning, g.c. can change physical position of the objects. If g.c. changes position of an object the pointer will point at wrong place in memory. To avoid such problems (connected with garbage collector) C# contains 'fixed' keyword. It informs system not to relocate an object by the garbage collector.

Examples of 'fixed':

// pt is a managed variable, subject to g.c.
Colour cl = new Colour();
// must use fixed to get address of cl.R
fixed ( int* pi = &cl.R)
{
*pi = 1;
}

To initialize multiple pointers all of the same type:

fixed (byte* pb = sarr, pd = darr) {...}

To initialize pointers of different type:

fixed (int* pi = &cl.G)
fixed (double* pd = &array[10])

If we forget 'fixed' keyword the compiler will warn us of our mistake but it is not so intelligent to warn us in the following situation. There is a serious bug in the following code in spite of the fact the compilation ends successful.

class Test
{
public int x;
}
unsafe class SimpleTest
{
[STAThread]
static void Main(string[] args)
{
Test test = new Test();
int* pi;
fixed (int* px = &test.x)
{
*px = 100;
pi = px;
}
Console.WriteLine("before g.c.: " + *pi);
System.GC.Collect(2);
Console.WriteLine("after g.c.: " + *pi);
}
}

On my computer results were:

before g.c.: 100
after g.c.: 132 

As we have seen there were two different values for the same pointer. In reality there is very little probability of getting difference between 'before g.c.' and 'after g.c.' because probability of starting garbage collector is very little. But as a rule we should avoid using pointers outside fixed block. In our case each use of pointer 'pi' outside fixed block might provide errors very difficult to diagnose.

C# pointers and WinApi

The most important advantage of using pointers is cooperation with binary code.

Many WinApi functions use pointers. For example GetComputerName (Kernel32.lib.) provides our computer name.

BOOL GetComputerName(LPTSTR lpBuffer, // computer name
LPDWORD lpnSize // size of name buffer);

Using of the GetComputerName demonstrate the following program

[System.Runtime.InteropServices.DllImport("Kernel32")]
static extern unsafe bool GetComputerName(byte* lpBuffer,long* nSize);
static void Main()
{
byte[] buffor = new byte[512];
long size = buffor.Length;
unsafe
{
long* pSize = &size;
fixed (byte* pBuffor = buffor)
{
GetComputerName(pBuffor,pSize);
}
}
System.Text.Encoding textEnc = new System.Text.ASCIIEncoding();
System.Console.WriteLine("Computer name: {0}",textEnc.GetString(buffor));
}

Conclusion

As we have seen pointers are very useful part of C# language. Using pointers are not difficult but should be used carefully because it might be cause of errors difficult to diagnose. Using pointers disturb garbage collector functioning especially when we use many pointers in our program. Therefore before using pointers we should consider it and maybe try to find some other ways.


Similar Articles