CodeKicks.com
Focus on Microsoft Technologies - Tutorials, Articles, Code Samples.

Thursday, August 24, 2006

Pointers in C#

This lesson gives a brief overview of pointers and their use in C#. It only scratches the surface of a complicated topic, however, so if you are new to pointers it is recommended that you do further reading before using them in your code. Luckily, pointers are only really needed in C# where execution speed is highly important.

Pointer Notation

A pointer is a variable that holds the memory address of another type. In C#, pointers can only be declared to hold the memory addresses of value types (except in the case of arrays - see below).

Pointers are declared implicitly, using the 'dereferencer' symbol *, as in the following example:

int *p;

[Note that some coders place the dereferencer symbol immediately after the type name, eg.

int* p;

This variation appears to work just as well as the previous one.]

This declaration sets up a pointer 'p', which will point to the initial memory address of an integer (stored in four bytes).

The combined syntactical element *p ('p' prefixed by the dereferencer symbol '*') is used to refer to the type located at the memory location held by p. Hence given its declaration, *p can appear in integer assignments like the following:

*p = 5;

This code gives the value 5 to the integer that was initialised by the declaration. It is important, however, not to confuse such an assignment with one in which the derefencer symbol is absent, e.g.

p = 5;

The effect of this assignment is to change the memory location held by p. It doesn't change the value of the integer initialised by the original declaration; it just means that p no longer points to that integer. In fact, p will now point to the start of the four bytes present at memory location 5.

Another important symbol for using pointers is the operator &, which in this context returns the memory address of the variable it prefixes. To give an example of this symbol, the following code sets up p to point to integer i's memory location:

int i = 5;
int *p;
p = &i;

Given the above, the code

*p = 10;

changes the value of i to 10, since '*p' can be read as 'the integer located at the memory value held by p'.

There is another important piece of notation for pointers. Pointers can be declared for structs (see lesson 11), as in the following example (which uses the 'Coords' struct defined further below):

Coords x = new Coords();
Coords *y = &x;

One can then use the declared pointer y to access a public field of x (say z). This would be done using either the expression

(*y).z

or the equivalent expression, which uses the -> string:

y -> z

Unsafe Code

A major problem with using pointers in C# is that C# operates a background garbage collection process. In freeing up memory, this garbage collection is liable to change the memory location of a current object without warning. So any pointer which previously pointed to that object will no longer do so. Such a scenario leads to two potential problems. Firstly, it could compromise the running of the C# program itself. Secondly, it could affect the integrity of other programs.

Because of these problems, the use of pointers is restricted to code which is explicitly marked by the programmer as 'unsafe'. Because of the potential for malicious use of unsafe code, programs which contain unsafe code will only run if they have been given full trust.

To address the problem of garbage collection, one can declare a pointer within a 'fixed' expression. This 'pins' the location of the type pointed to - the memory location of the type therefore remains static, safe from garbage collection. Note that the fixed statement can only be used within the context of unsafe code.

There is a further quirk to learn. Any value types declared within unsafe code are automatically 'fixed', and will generate compile-time errors if used within fixed expressions. The same is not true of reference types, however (for the difference between value and reference types see lesson 4).

The following code gives an example of a method marked 'unsafe'. From the previous paragraph it follows that the pointer p cannot be declared within a 'fixed' statement on line 9, because p is set up to point to the struct c (a value type) which is declared within the unsafe code

1.

using System;

2.

public struct Coords

3.

{

4.

    int x;

5.

    int y;

6.

    unsafe public static void Main()

7.

    {

8.

        Coords c = new Coords();

9.

        Coords *p = &c;

10.

        {

11.

            p->y = 6;

12.

            (*p).x = 5;

13.

        }

14.

        Console.WriteLine(c.y);

15.

        Console.WriteLine(c.x);

16.

    }

17.

}


Compare this with the following code, in which the pointer p on line 8 must be declared within a 'fixed' statment, because it is set up to point to a type which is not declared within the unsafe block of code:

1.

using System;

2.

public struct Coords

3.

{

4.

    int x;

5.

    int y;

6.

    unsafe public static void notMain(ref Coords c)

7.

    {

8.

        fixed (Coords *p = &c)

9.

        {

10.

            p->y = 6;

11.

            (*p).x = 5;

12.

        }

13.

        Console.WriteLine(c.y);

14.

        Console.WriteLine(c.x);

15.

    }

16.

}


In the examples given above, 'unsafe' is included as a method modifier. However, it can also be used within a code block, as in the following code fragment:

1.

using System;

2.

public static void Main()

3.

{

4.

    unsafe

5.

    {

6.

        Coords c = new Coords();

7.

        [...]

8.

    }

9.

}


Pointers, Methods and Arrays

Although we stated above that pointers can only be used with value types, an exception to this involves arrays (some authors state that the same exception applies to strings, but we have never been able to make this work).

A pointer can be declared in relation to an array, as in the following:

int[] a = {4, 5};
int *b = a;

What happens in this case is that the memory location held by b is the location of the first type held by a. This first type must, as before, be a value type. The code beneath shows that it is possible to step through the values of an array using a pointer, but explaining this further goes beyond the scope of this tutorial.

1.

using System;

2.

public class Tester

3.

{

4.

    public static void Main()

5.

    {

6.

        int[] a = {4, 5};

7.

        changeVal(a);

8.

        Console.WriteLine(a[0]);

9.

        Console.WriteLine(a[1]);

10.

    }

11.

    

12.

    public unsafe static void changeVal(int[] a)

13.

    {

14.

        fixed (int *b = a)

15.

        {

16.

            *b = 5;

17.

            *(b + 1) = 7;

18.

        }

19.

    }

20.

}

Continuee…

Post a Comment