Apuntadores

Los apuntadores son variables que contienen direcciones de memoria como sus valores. Por lo regular una variable contiene directamente un valor específico. Un apuntador, por parte, contiene la dirección de una variable que contiene un valor específico. En este sentido, un nombre de variable se refiere directamente a un valor y un apuntador indirectamente a un valor. El referirse a un valor a través de un apuntador se conoce como indirección.

Los apuntadores deben ser inicializados cuando son declarados o en un enunciado de asignación. Un apuntador puede ser inicializado a 0, NULL, o a una dirección. Un apuntador con valor NULL apunta a nada.

Código inseguro es todo aquél fragmento de código en C# dentro del cual es posible hacer uso de punteros.

Un puntero en C# es una variable que es capaz de almacenar direcciones de memoria. Generalmente suele usarse para almacenar direcciones que almacenen objetos, por lo que en esos casos su significado es similar al de variables normales de tipos referencia. Sin embargo, los punteros no cuentan con muchas de las restricciones de éstas a la hora de acceder al objeto. Por ejemplo, al accederse a los elementos de una tabla mediante un puntero no se pierde tiempo en comprobar que el índice especificado se encuentre dentro de los límites de la tabla, lo que permite que el acceso se haga más rápidamente.

Aparte de su mayor eficiencia, también hay ciertos casos en que es necesario disponer del código inseguro, como cuando se desea hacer llamadas a funciones escritas en lenguajes no gestionados cuyos parámetros tengan que ser punteros.

Es importante señalar que los punteros son una excepción en el sistema de tipos de .NET, ya que no derivan de la clase primigenia System.Object, por lo que no dispondrán de los métodos comunes a todos los objetos y una variable object no podrá almacenarlos (tampoco existen procesos similares al boxing y unboxing que permitan simularlo)

Compilación de códigos inseguros

El uso de punteros hace el código más proclive a fallos en tanto que se salta muchas de las medidas incluidas en el acceso normal a objetos, por lo que es necesario incluir ciertas medidas de seguridad que eviten la introducción accidental de esta inseguridad

La primera medida tomada consiste en que explícitamente hay que indicar al compilador que deseamos compilar código inseguro. Para ello, al compilador de línea de comandos hemos de pasarle la opción /unsafe, como se muestra el ejemplo:

csc códigoInseguro.cs /unsafe

Si no se indica la opción unsafe, cuando el compilador detecte algún fuente con código inseguro producirá un mensaje de error como el siguiente:

códigoInseguro(5,23): error CS0277: unsafe code may only appear if 
compiling with /unsafe

En caso de que la compilación se vaya a realizar a través de Visual Studio.NET, la forma de indicar que se desea compilar código inseguro es activando la casilla View/Property Pages/Configuration Properties/Build/Allow unsafe code blocks

Marcado de códigos inseguros

Aparte de forzarse a indicar explícitamente que se desea compilar código inseguro, C# también obliga a que todo uso de código inseguro que se haga en un fichero fuente tenga que ser explícitamente indicado como tal. A las zonas de código donde se usa código inseguro se les denomina contextos inseguros, y C# ofrece varios mecanismos para marcar este tipo de contextos.

Una primera posibilidad consiste en preceder un bloque de instrucciones de la palabra reservada unsafe siguiendo la siguiente sintaxis:

unsafe<instrucciones>

En el código incluido en podrán definirse variables de tipos puntero y podrá hacerse uso de las mismas. Por ejemplo:

public void f()
{
	unsafe
	{
	int *x;
	}
}

Otra forma de definir contextos inseguros consiste en añadir el modificador unsafe a la definición de un miembro, caso en que dentro de su definición se podrá hacer uso de punteros. Así es posible definir campos de tipo puntero, métodos con parámetros de tipos puntero, etc.

Definición de punteros

Para definir una variable puntero de un determinado tipo se sigue una sintaxis parecida a la usada para definir variables normales sólo que al nombre del tipo se le postpone un símbolo de asterisco (*) O sea, un puntero se define así:

<tipo> * <nombrepuntero>;

Por ejemplo, una variable puntero llamada a que pueda almacenar referencias a posiciones de memoria donde se almacenen objetos de tipo int se declara así:
int * a;

En caso de quererse declarar una tabla de punteros, entonces el asterisco hay que incluirlo tras el nombre del tipo pero antes de los corchetes. Por ejemplo, una tabla de nombre t que pueda almacenar punteros a objetos de tipo int se declara así:

int*[]  t;

Hay un tipo especial de puntero que es capaz de almacenar referencias a objetos de cualquier tipo. éstos punteros se declaran indicando void como . Por ejemplo:

void * punteroACualquierCosa;

Hay que tener en cuenta que en realidad lo que indica el tipo que se dé a un puntero es cuál es el tipo de objetos que se ha de considerar que se almacenan en la dirección de memoria almacenada por el puntero. Si se le da el valor void lo que se está diciendo es que no se desea que se considere que el puntero apunta a ningún tipo específico de objeto. Es decir, no se está dando información sobre el tipo apuntado.

Obtención de dirección de memoria. Operador &

Para almacenar una referencia a un objeto en un puntero se puede aplicar al objeto el operador prefijo &, que lo que hace es devolver la dirección que en memoria ocupa el objeto sobre el que se aplica. Un ejemplo de su uso para inicializar un puntero es:

int x =10;
int * px = &x;

Este operador no es aplicable a expresiones constantes, pues éstas no se almacenan en ninguna dirección de memoria específica sino que se incrustan en las instrucciones. Por ello, no es válido hacer directamente:

int px = &10;  // Error 10 no es una variable con dirección propia

Un puntero no almacena directamente un objeto sino que suele almacenar la dirección de memoria de un objeto (o sea, apunta a un objeto) Para obtener a partir de un puntero el objeto al que apunta hay que aplicarle al mismo el operador prefijo *, que devuelve el objeto apuntado. Por ejemplo, el siguiente código imprime en pantalla un 10:

int x = 10;
int * px= &x;
Console.WriteLine(*px);

Acceso a miembro de contenido de puntero. Operador ->

Si un puntero apunta a un objeto estructura que tiene un método F() sería posible llamarlo a través del puntero con:

(*objeto).F();

Sin embargo, como llamar a objetos apuntados por punteros es algo bastante habitual, para facilitar la sintaxis con la que hacer esto se ha incluido en C# el operador ->, con el que la instrucción anterior se escribiría así:

objeto->f();

Es decir, del mismo modo que el operador . permite acceder a los miembros de un objeto referenciado por una variable normal, -> permite acceder a los miembros de un objeto referenciado por un puntero. En general, un acceso de la forma O -> M es equivalente a hacer (*O).M. Por tanto, al igual que es incorrecto aplicar * sobre punteros de tipo void *, también lo es aplicar ->.