jueves, 29 de enero de 2009

Introducción a Punteros de C

Sólo pretendo introducir el concepto de los punteros en C de manera básica. A pesar de su gran utilidad, mucha gente deja de utilizarlos sólo por que "su uso no es tan simple como una variable común", pero como veremos, sigue siendo simple. Un puntero es una variable que alojará una dirección de memoria. En dicha dirección, generalmente habrá una variable, o una estructura (struct, union).

Escribiré un trozo de código, luego un comentario y luego una imagen al respecto. Verán que no soy buen dibujante, pero el sentido del gráfico es sólo ayudar a entender el código. Además el dibujo no se corresponde necesariamente con lo que realmente ocurre en la memoria.

Empezamos...

[sourcecode language='c']#include
#include
int * p = NULL;[/sourcecode]
Creamos un puntero a entero, llamado p. Éste puntero contendrá la dirección de memoria en la que deberá situarse una variable entera. Como un puntero es una variable como cualquiera de C, si no lo inicializamos, contendrá basura, y esto es una dirección de memoria incorrecta, y no sabemos qué podría ocurrir si intentamos acceder a ella. En síntesis, inicializamos el punteros a NULL.
[sourcecode language='c'] int * q = NULL;
int **d = NULL;
int v = 4;
int w = 3;[/sourcecode]
Creamos otro puntero a entero (q), y creamos un puntero a un puntero a entero (d) (no asustarse con éste, ya lo vemos). Y creamos dos enteros auxiliares.

punteros0



p = &v;
El operador &, en este contexto devuelve la dirección de memoria de una variable. Con ésta instrucción decimos, "p almacena la dirección de memoria de v". ERROR : p = v. Ésto dice "p almacena la dirección de memoria v" Que no sabemos dónde apunta!

punteros1



[sourcecode language='c']*p = 5;
printf("v = %d\n",v);[/sourcecode] El operador * accede al contenido apuntado por el puntero. En éste caso el puntero apunta a v, asi que la variable v quedará modificada al valor 5.

punteros2



[sourcecode language='c']q = p;
*q = 8;
printf("v = %d\n",v);
printf("*p = %d\n",*p);[/sourcecode]
El puntero q, ahora almacena la misma dirección que p. Por lo tanto ambos apuntan al mismo lugar (en este caso, v).

punteros3



d = &p;

El puntero d debe apuntar a otro puntero, así lo declaramos arriba. Entonces "d almacena la dirección de memoria de p" (NO es la dirección apuntada por p)

punteros4



*d = &w;
printf("*p=%d\n",*p);
Ahora p (el contenido apuntado por d) apunta hacia w y lo verificamos con printf. Veremos que nos muestra 3, el valor de w. Ahora q apunta a v, p apunta a w, y d apunta a p.

punteros5



[sourcecode language='c']**d = 7;
printf("w=%d\n",w);[/sourcecode]
Modificando el valor de w. Te das cuenta porqué? Accedemos primero a p y luego a su contenido apuntado.

punteros6



Memoria dinámica.


Hasta ahora sólo hicimos que los punteros apunten a memoria asignada por el compilador (las variables w y v) También se puede asignar memoria a los punteros dinámicamente. Éste es un tema muy profundo para esta introducción, de todas formas, va un ejemplo.
[sourcecode language='c']struct miestructura {
int a;
int * c;
}; //Esta declaración va fuera del main
struct miestructura * sp = NULL;
int * ep = NULL;[/sourcecode]

memo1



[sourcecode language='c']sp = calloc(1,sizeof(struct miestructura));
ep  = calloc(2,sizeof(int));
sp->c = NULL;[/sourcecode]
Creamos un puntero a estructura (sp) y un puntero a entero (ep). Luego a sp reservamos memoria para 1 estructura, y a ep le reservamos memoria para 2 enteros. La función calloc se encarga de ésto, el primer argumento es el número de elementos y el segundo el tamaño (en bytes) de cada uno. Hay muchas otras funciones para reservar memoria.
[sourcecode language='c'](*sp).a = 1;
sp->a = 1;[/sourcecode]
Las dos lineas anteriores son equivalentes. En la primera, accedemos al contenido apuntado por sp (que es una estructura) y luego accedemos al miembro 'a' de la estructura usando el operador .(punto). La segunda es una forma abreviada y usando el operador -> accedemos directamente a 'a'.

memo2



[sourcecode language='c']sp->c = calloc(1,sizeof(int)); [/sourcecode]
Completamos los restantes miembros de la estructura. Si bien *sp tiene memoria, aún no habíamos asignado memoria para el miembro 'c' que es un puntero.
[sourcecode language='c']ep[0] = 1;
ep[1] = 2;
*(sp->c) = 3; [/sourcecode]
Damos valores a los dos enteros que le habíamos reservado a ep. Luego le damos valor al entero apuntado por el miembro 'c'.

memo3



[sourcecode language='c']free(sp->c);
sp->c = NULL;[/sourcecode]
Liberamos la memoria de sp->c y lo hacemos "apuntar a ningún lado"

memo4



[sourcecode language='c']free(sp);
sp=NULL;[/sourcecode]

memo5



[sourcecode language='c']free(ep);[/sourcecode]
Luego liberamos la memoria de sp y lo anulamos. Notar que el orden en que se libera la memoria es muy importante. Si hubiesemos liberado primero a sp, no podríamos (en este caso) liberar a 'c' pues no podríamos acceder a él. En éste ultimo caso hacer sp->c nos hubiera dado en general error de violación de segmento. El código (por simplicidad) no está controlando que calloc pueda encontrar memoria para reservar. En cualquier programa serio hay que controlar los valores de retorno.

memo6



Y listo por ahora ! Espero que les sirva. Saludos Leo

No hay comentarios:

Publicar un comentario