Deep C# - What's The Matter With Pointers?
Written by Mike James   
Thursday, 21 November 2019
Article Index
Deep C# - What's The Matter With Pointers?
Inherent dangers
Fixed Arrays
Memory Allocation

Banner

Pointers in C#

Pointers in C#' are a generalisation of the reference type to include pointer arithmetic. 

In other words, a reference that you can do arithmetic with, is a pointer. 

 

Pointers are so dangerous that they have to be quarantined within your code. First the entire project has to be marked as unsafe by using the Project Properties to set the "Build, Allow Unsafe Code" flag. Then any use of pointers has to be enclosed in an usafe{} block to mark it out even more clearly.

Even more restrictive is the fact that you cannot create a pointer to anything you care to point at, only to a restricted subset of types that have a simpler way of using memory so making pointer use slightly less tricky.

Essentially you can only create a pointer to any simple value type, e.g. int, float, char, to an enum, to another pointer or to a struct that doesn’t contain other managed types.  So you can’t have a pointer to an object, or to a delegate or to a reference.

This is restrictive and basically amounts to not allowing pointers to anything created on the heap or subject to dynamic memory management.

However you can have a pointer to a struct that contains simple value types and you can create pointers to arrays of simple value types. 

This is generally enough to allow pointers to be used to work with legacy implementations of data structures or to work with binary data structures that have been received over the network or from a file. 

You can also have a pointer of type void, i.e. a pointer to an unknown type but to be of any use in, i.e. to use pointer arithmetic, you need to cast a void pointer to a pointer to a given type.

To declare a pointer type C# uses the C++ like syntax:

type* variable;

The * is the dereferencing or indirection operator and is generally used in conjunction with the address of operator &, which as its name suggests, returns the address of a variable.

For example:

unsafe{
 int* pMyInt;
 int MyInt;
 pMyInt = &MyInt;
}

creates a pointer to an integer, i.e. pMyInt and stores the address of the integer MyInt in the pointer.

The first important thing to note is that a pointer does not inherit from an object and so there are no methods associated with it and no boxing and unboxing. For example, you can’t use a ToString() method call to display the value of a pointer.

What you can do, however, is to use a cast to convert a pointer to a more usual data type. So, to display the contents of the pointer you would use something like:

MessageBox.Show(((int)pMyInt).ToString());

Of course this assumes that the current implementation of int is large enough to contain a pointer, i.e. a machine address. Notice that this is not deferencing the pointer but showing you the pointer's contents. Also notice that the fact that the way that machine represents an address comes into consideration and this is a reflection of the fact that pointers are a low level construct. 

The indirection operator returns the values stored at the address that a pointer is pointing at. For example:

MessageBox.Show((*pMyInt).ToString());

displays the current contents of MyInt, i.e. the value of whatever pMyInt is pointing at. 

That is *pMyInt is an int.

The indirection operator and the address of operator really are the inverse of one another.

That is:

MessageBox.Show((*&MyInt).ToString());

just displays the content of MyInt. 

 

pointer

 

A pointer can be null and applying the indirection operator in this case generates an exception. Obviously it makes no sense to use indirection on a void pointer, what would the data type of the result be, but you can always cast a void pointer and then use indirection.

Notice that this process can produce complete nonsense. For example, consider:

void* pMyData = &MyInt;
MessageBox.Show((*(double*)pMyData).ToString());

This sets a void pointer to an integer, i.e. a 32-bit integer, then casts it to a double pointer, i.e. double*, and finally uses the indirection operator to return the value so pointed at.

If you try this out you will find that it works but it is mostly nonsense because the original int was only 4 bytes of storage and the double is 8 bytes.

Where did the additional 4 bytes come from?

The answer is that you have succeeded in reading data from a neighbouring memory location, one that you normally would not be able to access. Of course, reading from a memory location that you don’t understand is fairly safe, but who knows what the effect of writing to such a location is going to have.

This is the reason pointers are considered unsafe.

For example, try:

int MyInt2 = 0;
int MyInt=1234;
void* pMyData = &MyInt;
*(double*)pMyData = 123456.789;
MessageBox.Show(MyInt2.ToString());

You might be surprised to discover that the value of MyInt2 has changed and is no longer zero, even though it isn’t assigned a new value within the program.

The simple explanation is that MyInt2 is allocated storage alongside MyInt and when we assign an 8-byte value to MyInt the extra 4 bytes overwrite MyInt2.

This is clearly dangerous, unexpected and usually unwanted behaviour and as already mentioned, but it cannot be said too often - it is in this sense that the code is “unsafe”.

One very common use of pointers is to get at the internals of a data type. For example, suppose you want to retrieve the four bytes that make up a 32-bit integer:

int MyInt = 123456789;

We can always use a void pointer to get the address of any variable:

void* MyPointer;
MyPointer = &MyInt;

Then we can cast it to a pointer to any of the standard types and use pointer arithmetic, in this case to a byte which is then converted to a char:

byte* pMyByte = (byte*)MyPointer;
char MyChar = (char)*(pMyByte + 3);
MessageBox.Show(MyChar.ToString());

The reason we don’t go directly to a char pointer is that a char is two bytes in size and we are converting a 4-byte int to four ASCII byte characters rather than Unicode two byte characters.

In most cases there are managed ways of gaining access to the internal structure of the common data types using either the Convert or BitConvertor classes. In this case the BitConvertors GetBytes method can be used to convert the int to a byte array and then any of the bytes can be converted to a char using the Convert class:

Byte[] Bytes=BitConverter.GetBytes(MyInt);
MyChar = Convert.ToChar(Bytes[3]);

As long as there is a GetBytes method that will convert the data type into a byte array you don’t need to use pointers.

Multiple indirection

If you think you have got the idea of indirection then it’s time to put you to the test. The real mark of a pointer expert is being able to handle double, treble and more… indirection.

In theory this is easy.

For example,

int** ppMyInt;
int* pMyInt;
int MyInt=1234;
pMyInt = &MyInt;
ppMyInt = &pMyInt;
MessageBox.Show((**ppMyInt).ToString());

In this case we declare a pointer to a pointer, i.e. ppMyInt and use it to point at pMyInt.

To display the value pointed at by pMyInt, i.e. the value in MyInt, we have to use two levels of indirection as in **ppMyInt. In this case double indirection is fairly easy to follow but in real cases it can become very difficult to work out when you need a pointer or a pointer to a pointer and so on.

 

 

Banner

 

<ASIN:1449320104>

<ASIN:1118833031>

<ASIN:1449367569>

<ASIN:1449343503>



Last Updated ( Saturday, 23 November 2019 )