You are on page 1of 5

Manipulacin de punteros

Obtencin de direccin 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 direccin 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 direccin de memoria especfica sino que se incrustan en las instrucciones. Por ello, no es vlido hacer directamente:
int px = &10; // Error 10 no es una variable con direccin propia

Tampoco es vlido aplicar & a campos readonly, pues si estos pudiesen ser apuntados por punteros se correra el riesgo de poderlos modificar ya que a travs de un puntero se accede a memoria directamente, sin tenerse en cuenta si en la posicin accedida hay algn objeto, por lo que mucho menos se considerar si ste es de slo lectura. Lo que es s vlido es almacenar en un puntero es la direccin de memoria apuntada por otro puntero. En ese caso ambos punteros apuntaran al mismo objeto y las modificaciones a ste realizadas a travs de un puntero tambin afectaran al objeto visto por el otro, de forma similar a como ocurre con las variables normales de tipos referencia. Es ms, los operadores relacionales tpicos (==, !=, <, >, <= y >=) se han redefinido para que cuando se apliquen entre dos punteros de cualesquiera dos tipos lo que se compare sean las direcciones de memoria que estos almacenan. Por ejemplo:
int x = 10; int px = &x; int px2 = px; // px y px2 apuntan al objeto almacenado en x Console.WriteLine( px == px2); // Imprime por pantalla True

En realidad las variables sobre las que se aplique & no tienen porqu estar inicializadas. Por ejemplo, es vlido hacer:
private void f() { int x; unsafe { int px = &x;} }

Esto se debe a que uno de los principales usos de los punteros en C# es poderlos pasar como parmetros de funciones no gestionadas que esperen recibir punteros. Como muchas de esas funciones han sido programadas para inicializar los contenidos de los punteros que se les pasan, pasarles punteros inicializados implicara perder tiempo innecesariamente en inicializarlos.

Acceso a contenido de puntero. Operador *


Un puntero no almacena directamente un objeto sino que suele almacenar la direccin 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 cdigo imprime en pantalla un 10:
int x = 10; int * px= &x; Console.WriteLine(*px);

Es posible en un puntero almacenar null para indicar que no apunta a ninguna direccin vlida. Sin embargo, si luego se intenta acceder al contenido del mismo a travs del operador * se producir generalmente una excepcin de tipo NullReferenceException (aunque realmente esto depende de la implementacin del lenguaje) Por ejemplo:
int * px = null; Console.WriteLine(*px); // Produce una NullReferenceException

No tiene sentido aplicar * a un puntero de tipo void * ya que estos punteros no almacenan informacin sobre el tipo de objetos a los que apuntan y por tanto no es posible recuperarlos a travs de los mismos ya que no se sabe cuanto espacio en memoria a partir de la direccin almacenada en el puntero ocupa el objeto apuntado y, por tanto, no se sabe cuanta memoria hay que leer para obtenerlo.

Acceso a miembro de contenido de puntero. Operador ->


Si un puntero apunta a un objeto estructura que tiene un mtodo F() sera posible llamarlo a travs 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 instruccin anterior se escribira 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 *, tambin lo es aplicar ->

Conversiones de punteros
De todo lo visto hasta ahora parece que no tiene mucho sentido el uso de punteros de tipo void * Pues bien, una utilidad de este tipo de punteros es que pueden usarse como almacn de punteros de cualquier otro tipo que luego podrn ser recuperados a su tipo original usando el operador de conversin explcita. Es decir, igual que los objetos de tipo object pueden almacenar implcitamente objetos de cualquier tipo, los punteros void * pueden almacenar punteros de cualquier tipo y son tiles para la escritura de mtodos que puedan aceptar parmetros de cualquier tipo de puntero. A diferencia de lo que ocurre entre variables normales, las conversiones entre punteros siempre se permiten, al realizarlas nunca se comprueba si son vlidas. Por ejemplo:
char c = 'A'; char* pc = &c; void* pv = pc; int* pi = (int*)pv; int i = *pi;// Almacena en 16 bits del char de pi + otros 16 // indeterminados Console.WriteLine(i); *pi = 123456; // Machaca los 32 bits apuntados por pi

En este cdigo pi es un puntero a un objeto de tipo int (32 bits), pero en realidad el objeto al que apunta es de tipo char (16 bits), que es ms pequeo. El valor que se almacene en i es en principio indefinido, pues depende de lo que hubiese en los 16 bits extras resultantes de tratar pv como puntero a int cuando en realidad apuntaba a un char. Del mismo modo, conversiones entre punteros pueden terminar produciendo que un puntero apunte a un objeto de mayor tamao que los objetos del tipo del puntero. En estos casos, el puntero apuntara a los bits menos significativos del objeto apuntado. Tambin es posible realizar conversiones entre punteros y tipos bsicos enteros. La conversin de un puntero en un tipo entero devuelve la direccin de memoria apuntada por el mismo. Por ejemplo, el siguiente cdigo muestra por pantalla la direccin de memoria apuntada por px:
int x = 10; int *px = &x; Console.WriteLine(( int) px);

Por su parte, convertir cualquier valor entero en un puntero tiene el efecto de devolver un puntero que apunte a la direccin de memoria indicada por ese nmero. Por ejemplo, el siguiente cdigo hace que px apunte a la direccin 1029 y luego imprime por pantalla la direccin de memoria apuntada por px (que ser 1029):
int *px = (int *) 10; Console.WriteLine(( int) px);

Ntese que aunque en un principio es posible hacer que un puntero almacene cualquier direccin de memoria, si dicha direccin no pertenece al mismo proceso que el cdigo en que se use el puntero se producir un error al leer el contenido de dicha direccin. El tipo de error ha producir no se indica en principio en la especificacin del lenguaje, pero la implementacin de Microsoft lanza una referencia NullReferenceException. Por ejemplo, el siguiente cdigo produce una excepcin de dicho tipo al ejecurtase:
using System; class AccesoInvlido { public unsafe static void Main() { int * px = (int *) 100; Console.Write (*px); // Se lanza NullReferenceException } }

Aritmtica de punteros Los punteros se suelen usar para recorrer tablas de elementos sin necesidad de tener que comprobarse que el ndice al que se accede en cada momento se encuentra dentro de los lmites de la tabla. Por ello, los operadores aritmticos definidos para los punteros estn orientados a facilitar este tipo de recorridos. Hay que tener en cuenta que todos los operadores aritmticos aplicables a punteros dependen del tamao del tipo de dato apuntado, por lo que no son aplicables a punteros void * ya que estos no almacenan informacin sobre dicho tipo. Esos operadores son:
y

++ y --: El operador ++ no suma uno a la direccin almacenada en un puntero, sino que le suma el tamao del tipo de dato al que apunta. As, si el puntero apuntaba a un elemento de una tabla pasar a apuntar al siguiente (los elementos de las tablas se almacenan en memoria consecutivamente) Del mismo modo, -resta a la direccin almacenada en el puntero el tamao de su tipo de dato. Por ejemplo, una tabla de 100 elementos a cuyo primer elemento inicialmente apuntase pt podra recorrerse as:

for (int i=0; i<100; i++) Console.WriteLine( "Elemento{0}={1}" , i, (*p)++);

El problema que puede plantear en ciertos casos el uso de ++ y -- es que hacen que al final del recorrido el puntero deje de apuntar al primer elemento de la tabla. Ello podra solucionarse almacenando su direccin en otro puntero antes de iniciar el recorrido y restaurndola a partir de l tras finalizarlo.
y

+ y -: Permiten solucionar el problema de ++ y -- antes comentado de una forma ms cmoda basada en sumar o restar un cierto entero a los punteros. + devuelve la direccin resultante de sumar a la direccin almacenada en el puntero sobre el que se aplica el tamao del tipo de dicho puntero tantas veces como indique el entero sumado. - tiene el mismo significado pero r estando dicha cantidad en vez de sumarla. Por ejemplo, usando + el bucle anterior podra rescribirse as:

for (int i=0; i<100; i++) Console.WriteLine( "Elemento{0}={1}" , i, *(p+i));

El operador - tambin puede aplicarse entre dos punteros de un mismo tipo, caso que devuelve un long que indica cuntos elementos del tipo del puntero pueden almacenarse entre las direcciones de los punteros indicados.
y

en

[]: Dado que es frecuente usar + para acceder a elementos de tablas, tambin se ha redefinido el operador [] para que cuando se aplique a una tabla haga lo mismo y devuelva el objeto contenido en la direccin resultante. O sea *(p+i) es equivalente a p[i], con lo que el cdigo anterior equivale a:

for (int i=0; i<100; i++) Console.WriteLine("Elemento{0}={1}" , i, p[i]);

No hay que confundir el acceso a los elementos de una tabla aplicando [] sobre una variable de tipo tabla normal con el acceso a travs de un puntero que apunte a su primer elemento. En el segundo caso no se comprueba si el ndice indicado se encuentra dentro del rango de la tabla, con lo que el acceso es ms rpido pero tambin ms proclive a errores difciles de detectar. Finalmente, respecto a la aritmtica de punteros, hay que tener en cuenta que por eficiencia, en las operaciones con punteros nunca se comprueba si se producen desbordamientos, y en caso de producirse se truncan los resultados sin avisarse de ello mediante excepciones. Por eso hay que tener especial cuidado al operar con punteros no sea que un desbordamiento no detectado cause errores de causas difciles de encontrar.