Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
ndice 1. Introduccin 2. Operaciones 3. Aritmtica de punteros 4. Punteros y vectores/matrices 5. Punteros y estructuras 6. Asignacin dinmica de memoria 7. Punteros y funciones: Paso por referencia Devolucin de punteros Vectores/matrices como parmetros Punteros a funcin
Tema 13
Punteros
Pg 1
Introduccin Los punteros son uno de los tipos de datos ms temidos de C. Sin embargo, tambin son los ms verstiles y los que mayor potencia proporcionan. Su uso permite el paso de argumentos por referencia, la construccin de estructuras dinmicas de datos y la mejora en la velocidad de ejecucin de algunos algoritmos. Sin embargo, mal usados pueden producir errores graves, difciles de detectar y, en ocasiones, los peores errores posibles: los no reproducibles. Un puntero es una variable que contiene una direccin de memoria. En la mayora de los sistemas operativos actuales, se suele tratar de un nmero entero de cuatro u ocho bytes (32 o 64 bits). Recordemos de temas anteriores que todo objeto de un programa se debe situar en memoria principal (ya sea en la pila, en el montn, en el segmento de cdigo o en el de datos). Cada objeto del programa tiene, por tanto, una posicin de memoria (aquella en la que comienza a almacenarse) que lo identifica. Los punteros se usan para almacenar posiciones (direcciones) de memoria de otros objetos del programa. Un puntero puede considerarse como una referencia a otro objeto del
Tema 13 Punteros Pg 2
programa (una variable, una funcin...). Los punteros son variables, exactamente igual que las dems. Se declaran junto con el resto, usando la siguiente sintaxis:
<tipo> *<nombre>;
Donde <nombre> es el nombre de la variable puntero y tipo es el tipo de objeto cuya direccin se va a almacenar. Es importante que se sepa a qu va a apuntar un puntero, por motivos que veremos luego. Ejemplos:
int *punt; /* puntero a entero */ float *v, *w, *z; /* punts. a real */ struct coche *a, *b; /* punts. a estructura */ void *pos; /* puntero genrico */
Los punteros a void son un tipo especial de puntero que no estn asociados a un tipo particular. Una vez declarado, a un puntero se le pueden asignar valores, se pueden realizar operaciones con l, se puede usar su valor en expresiones...
Tema 13
Punteros
Pg 3
Operaciones Sobre una variable puntero se pueden realizar una serie de operaciones que veremos a continuacin: Direccin: el operador de direccin no es realmente uno que se aplique sobre las variables puntero (normalmente), sino sobre otros tipos de variable. ste operador, representado con el smbolo ampersand (&) obtiene la direccin de memoria de la variable a la que precede. As, si la variable entera w est almacenada en la posicin de memoria 32012, la operacin
int *punt; punt= &w;
asignar el valor 32012 a la variable punt. Indireccin: el operador de desreferenciacin o de indireccin s se aplica a valores de tipo puntero. ste operador se representa por un asterisco (*) y devuelve un valor del tipo apuntado por el operando. Este valor es el contenido en la posicin apuntada por el puntero. As, en el siguiente cdigo
float *p; float q= 1.44; p= &q; print("%f\n", *p);
Tema 13 Punteros Pg 4
el valor que se imprime es 1.44, ya que p apunta a la direccin de q. En general, si el puntero x apunta a un tipo de datos T, la expresin *x es de tipo T. Incremento, decremento: los valores de tipo puntero se pueden incrementar y decrementar, siempre en valores enteros. Se admiten los operadores +, , ++ y . Resta de punteros: se puede hallar la diferencia entre dos punteros, que es la distancia que separa las direcciones a las que apuntan en memoria. Impresin: para mostrar el valor de un puntero se usa un especificador de formato especial en la instruccin printf: %p. nicamente se permiten estas operaciones entre punteros. Aritmtica de punteros Las operaciones de incremento, decremento y resta de punteros tienen una semntica especial. Cuando un puntero p se incrementa o decrementa en una cantidad X, no se modifica la direccin apuntada en X bytes, sino en X elementos del tipo apuntado por p. Es decir, p se incrementa o decrementa en
Tema 13 Punteros Pg 5
La resta de punteros debe hacerse siempre entre punteros del mismo tipo (es decir, que apunten al mismo tipo de datos) y el resultado no es el nmero de bytes que los separan, sino el nmero de elementos. Punteros y vectores/matrices Hay que hacer unas cuantas consideraciones acerca de cmo entiende C los vectores: Para C, un vector es un puntero que apunta a una zona de memoria reservada en tiempo de compilacin. El nombre de un vector es un puntero al inicio del mismo. Cuando C analiza una expresin del tipo
vector[indice]
En cuanto a las matrices, C las almacena en memoria disponiendo sus elementos por filas de forma consecutiva. As, los elementos de una matriz de 3x4 estaran as:
m00 m01 m02 m03 m10 m11 m12 m13 m20 m21 m22 m23
Por otra parte, pudiera parecer que una matriz de dos dimensiones es un vector de vectores (o de punteros, que es igual), y que C la almacena as:
m0 m1 m2 m10 m11 m12 m13 m20 m21 m22 m23 m00 m01 m02 m03
Tema 13
Punteros
Pg 7
Donde las filas podran estar en zonas no contiguas de memoria. Sin embargo, no es as. Todos los elementos estn seguidos en la memoria. Pero, como caso particular, las expresiones m[0], m[1] y m[2] sobre la matriz de nuestro ejemplo producen las direcciones donde comienzan las filas primera, segunda y tercera respectivamente. Es decir:
int m[3][4], *fila, f, c; for(f=0; f<3; f++) { fila= m[f]; for(c= 0; c<4; c++) printf("%d\n", fila[c]); }
Pero recordemos que esto no es porque m sea un vector de punteros a vectores, sino porque el compilador nos facilita la tarea permitindonos hablar de m[f]. Sabiendo que las matrices se almacenan de forma contigua, una forma bastante comn de acceder a un elemento de una matriz bidimensional de FxC a partir de la posicin de inicio de la matriz es mediante la frmula siguiente:
elemento(fila, columna)= *(puntero+(fila*C)+columna)
Esto nos permitir, como veremos ms tarde, pasar matrices de cualquier tamao a una funcin.
Tema 13 Punteros Pg 8
Punteros y estructuras Un puntero puede estar asociados a cualquier tipo de datos, includas estructuras (y, por supuesto, punteros). Cuando se tiene un puntero a estructura, se puede acceder a los campos de la misma de dos formas diferentes: desreferenciando el puntero y aplicando el operador . o directamente sobre el puntero, aplicando el operador >:
struct s { int a; float b; }; struct s var, *punt; punt= &var; var.a= 143; printf("%d, %d, %d\n", var.a, (*punt).a, punt>a );
Debe notarse que, debido a que la precedencia del operador . es superior a la del operador *, hay que usar parntesis para desreferenciar antes de acceder al campo. Los punteros a estructuras son muy usados cuando se trata con estructuras dinmicas de datos, tal y como veremos en temas posteriores.
Tema 13
Punteros
Pg 9
Asignacin dinmica de memoria Uno de los usos ms comunes de los punteros es mantener referencias a zonas de memoria dinmica. Tal y como se vio en un tema anterior, existe una zona de la memoria asignada al programa que se utiliza para almacenamiento de datos dinmicos. La gestin de los datos dinmicos se realiza mediante llamadas al sistema que permiten reservar y liberar zonas de memoria. Estas llamadas suelen devolver o aceptar como parmetros valores de tipo puntero. Para la gestin de memoria dinmica en C se usan varias funciones, cuyos prototipos estn declarados en el fichero de cabecera stdlib.h. En primer lugar, la funcin
void *malloc( int n )
permite reservar una zona de memoria dinmica de n bytes de longitud. La direccin de inicio de dicha zona se devuelve como resultado. Si no se consigui reservar esa cantidad de bytes, se devuelve el valor NULL. Como se ve, el tipo de retorno de malloc es un puntero genrico (void *), que deber ser convertido al tipo adecuado segn lo que se vaya a almacenar en la zona reservada.
Tema 13 Punteros Pg 10
Un ejemplo del uso de malloc para reservar espacio para un valor float sera ste:
float *p; p= (float *)malloc( sizeof(float) ); if( p==NULL ) { perror("Fallo en malloc()"); exit(1); } else *p= 3.141592;
Debe observarse que, al ser un vector de estructuras, no se necesita el operador >, puesto que la notacin p[x] devuelve una estructura, no un puntero.
Tema 13 Punteros Pg 11
Una macro que puede ser de utilidad para agilizar las llamadas a malloc es la siguiente:
#define NEW(x,n) (x*)malloc(n*sizeof(x)) int *p; struct s *vec; p= NEW(int, 1); vec= NEW(struct s, 120);
Sigue siendo necesario, por supuesto, comprobar si malloc ha retornado NULL. La direccin devuelta por malloc est siempre convenientemente alineada para almacenar cualquier tipo de dato (en las arquitecturas que necesitan alineacin), evitando as la posibilidad de un error de bus (ver tema de gestin de memoria en tiempo de ejecucin). Una funcin alternativa a malloc es
void *calloc( int tam, int cant )
que reserva un nmero de bytes igual a tam*cant y pone a cero el espacio reservado antes de devolver la direccin de inicio. Si se necesita cambiar el tamao de una zona de memoria reservada por malloc o calloc, ya sea para ampliarlo o para reducirlo, se puede usar la funcin
Tema 13 Punteros Pg 12
que cambia el tamao de la zona apuntada por ant a tam bytes y devuelve la posicin de la nueva zona. El cambio de tamao puede implicar mover los datos dentro de la memoria, y la propia funcin se ocupa de ello. Si no se puede reservar la nueva memoria, realloc devuelve NULL pero los datos originales siguen en su sitio (ant). Cuando se usan pequeas cantidades de memoria dinmica en un programa que va a ejecutarse durante poco tiempo, no se suelen plantear problemas. Pero si la cantidad de memoria dinmica a usar es grande o el programa debe estar ejecutndose de manera indefinida, puede suceder que se agote la memoria asignada al programa. Por lo tanto, y en cualquier caso, deben liberarse las zonas de memoria dinmica que se dejen de utilizar. Para ello se usa la funcin
void free( void *zona )
Al terminar un programa se libera automticamente toda la memoria que ocupa, por lo que en programas cortos no sera necesario usar free. Sin embargo, es aconsejable hacerlo para acostumbrarse. Cuando un programa largo olvida liberar parte de la memoria dinmica que reserva,
Tema 13 Punteros Pg 13
se produce lo que se llama una fuga de memoria (memory leak) que puede acabar por agotar la memoria asignada y detener el programa. Existen bibliotecas de funciones que substituyen malloc, calloc, realloc y free por versiones especiales que, aunque ms lentas, llevan una contabilidad de las zonas de memoria reservadas y liberadas y permiten detectar las fugas de memoria. Punteros y funciones Paso por referencia: Uno de los usos de los punteros es el paso de parmetros por referencia a una funcin. Recordemos que los parmetros de una funcin se pueden pasar por valor o por referencia. Si se pasan por valor, las modificaciones que se hagan sobre ellos no se vern en el programa principal. Si se pasan por referencia, s se reflejan en el programa principal estas modificaciones. En C, todos los parmetros de las funciones se pasan por valor, sin excepcin. Para simular un paso de parmetro por referencia en C, lo que se hace es pasar un puntero al objeto que se pasa. As, la funcin tiene acceso no slo al valor del parmetro sino tambin a su situacin en memoria, lo que permite su modificacin.
Tema 13 Punteros Pg 14
El paso por referencia posibilita que una funcin pueda generar ms de un valor. La funcin devolver (con return) uno de los valores de retorno y almacenar en parmetros pasados por referencia el resto de los valores. Un ejemplo:
int JulianoAdma( int jul, int *dia, int *mes, int *anno )
Esta funcin convierte una fecha expresada en das Julianos (una forma de representar fechas) a da, mes y ao. Devuelve cero si todo ha ido bien y 1 si se produjo la fecha juliana no es vlida. Como se ve en este ejemplo, si una funcin devuelve varios valores, se suele utilizar el principal (el devuelto por return) como cdigo de error y los parmetros por referencia para el resto de los valores.
Tema 13
Punteros
Pg 15
Devolucin de punteros: Una funcin puede retornar un tipo de datos puntero. La funcin se declarara as
tipo *funcin( argumentos )
Este tipo de funciones se suelen usar para reservar memoria o crear elementos en estructuras dinmicas de datos. Debe notarse que cuando una funcin devuelve una direccin de un objeto reservado con malloc no hay por qu declarar nada esttico. La memoria dinmica es global, y est accesible desde cualquier punto del programa, siempre que se tenga el puntero adecuado. Vectores/matrices como parmetros: Como se ha dicho ms arriba, C considera que un vector (o una matriz, es igual) es un puntero que seala a una zona de memoria reservada en tiempo de compilacin y capaz de albergar los elementos del mismo. Y el nombre del vector es equivalente a la direccin de comienzo de esta zona. Por lo tanto, pasar un vector o una matriz a una funcin es lo mismo que pasarle un puntero al tipo almacenado en el vector o matriz. En la llamada a
Tema 13 Punteros Pg 16
la funcin se colocar como parmetro real el nombre de la variable, y en la declaracin y definicin se usar, en caso de que se trate de un vector, una de estas notaciones:
void funcion( int *vector ) void funcion( int vector[] ) void funcion( int vector[TAMANO] )
Como se ve, en el caso de los vectores se puede obviar el tamao. No es as en el caso de las matrices, ya que para acceder a un elemento de una matriz, segn la frmula que vimos anteriormente, se deben conocer al menos todas las dimensiones de la matriz salvo la primera. Por lo tanto, si se quiere construir una funcin que trabaje con matrices de tamao fijo y conocido, se puede declarar de una de estas formas:
void funcion( float mat[FILAS][COLS] ) void funcion( float mat[][COLS] )
Pero si se quiere crear una funcin genrica (algo muy recomendable) que permita trabajar con cualquier tamao de matriz, se declarara como
void funcion( float *mat, int f, int c )
(se debe especificar obligatoriamente el nmero de columnas, aunque las filas se suelen pasar tambin). En el interior de la funcin, cada elemento mat[fila][col] se accedera as:
Tema 13 Punteros Pg 17
*(mat+fila*c+col)
Punteros a funcin: En C se puede tambin mantener en un puntero la direccin de inicio del cdigo de una funcin. Este puntero es un puntero a funcin. Se declara as:
retorno (*punt) ( argumentos );
Donde <retorno> es el tipo de datos que devuelve la funcin y <argumentos> es la lista de parmetros de la misma. As, para declarar un puntero a una funcin que devuelve un entero y acepta una cadena, un float y un vector de floats, tendremos:
int (*pt)(char v[]); *cad, float x, float
Y para asignar un valor a un puntero a funcin necesitamos a) una funcin del mismo tipo al que apunta el puntero y b) obtener la direccin de comienzo de dicha funcin en memoria. Supongamos que tenemos la funcin
int f( char *nom, float vel, float z[]);
Tema 13
Punteros
Pg 18
Las aplicaciones de los punteros a funcin son mltiples. Propondremos dos fragmentos de cdigo de ejemplo. En el primero, se crea una funcin tabula() que acepta un valor inicial, un valor final, un incremento y un puntero a funcin. Muestra una tabla de valores f(inicial), f(inicial+incremento)... f(final) para la funcin cuyo puntero se haya pasado:
void tabula( double ini, double fin, double inc, double (*fun)(double x) ); double prueba(double arg); ... tabula(1, 100, 1, sin); tabula(10, 10, 0.1, prueba); tabula(0, 10, 0.2, log); ... void tabula( double ini, double fin, double inc, double (*fun)(double x) )
Tema 13 Punteros Pg 19
Y llamar a
tabula(1, 10, 0.2, vec[loquesea]);
El segundo ejemplo utiliza dos funciones de la biblioteca estndar de C: qsort() y bsearch(). Estas funciones aplican los algoritmos de ordenacin Quicksort y de bsqueda dicotmica, respectivamente, sobre un vector. Los prototipos son:
void qsort( void *vec, int n, int tam, int (*comp)(void *a, void *b) ); void *bsearch( void *clave, void *vec, int n, int tam, int (*comp_c)(void *a, void *b);
La funcin qsort tiene como parmetros un puntero genrico que ser la direccin de comienzo del
Tema 13 Punteros Pg 20
vector a ordenar, el nmero de elementos del mismo (n), el tamao en bytes de cada elemento (tam) y un puntero a la funcin de comparacin comp(). La funcin de comparacin recibe dos punteros a los dos elementos que deben compararse. Devolver un nmero negativo si el primero es menor que el segundo, un nmero positivo si el segundo es mayor que el primero y cero si son iguales. Un ejemplo de funcin de comparacin para nmeros reales es ste:
int comparar( void *a, void *b ) { float x, y; x= *( (float*)a ); y= *( (float*)b ); if( x<y ) return 1; else if( x>y ) return +1; else return 0; }
Los argumentos de la funcin bsearch() son un puntero a la clave que se quiere buscar, un puntero al vector en el que se realizar la bsqueda, el nmero de elementos del mismo, el tamao de cada uno y un puntero a la funcin de comparacin. Esta funcin recibe dos parmetros: un puntero a la
Tema 13 Punteros Pg 21
clave a buscar y otro puntero a uno de los elementos del vector. Al igual que la funcin anterior, debe devolver un entero negativo, positivo o cero en funcin de la comparacin de la clave a buscar con la del elemento que se le pasa.
Tema 13
Punteros
Pg 22