Sei sulla pagina 1di 22

Tema 13 Punteros

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

sizeof(T) bytes. As, por ejemplo (suponiendo sizeof(char)==1 y sizeof(float)==4):


char *p= 20232; float *q= 20232; p++; /* p vale 20233 */ q++; /* q vale 20236 */ p= p+5; /* p vale 20238 */ q= q+2; /* q vale 20246 */

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]

realmente la convierte a otra de tipo


*(vector+indice)
Tema 13 Punteros Pg 6

Y, cosa curiosa, la mayora de los compiladores de C admiten las expresiones


indice[vector]

Un ejemplo que ilustra estos puntos:


char cadena[80], *p; strcpy(cadena, "palabra"); p= cadena; /* el nombre es el puntero */ /* imprimen lo mismo */ printf("%c, %s\n", cadena[5], cadena ); printf("%c, %s\n", *(p+5), p );

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;

Otro ejemplo, en el que se reserva espacio para un vector de 10 estructuras, sera:


struct s { int a; float b; }; struct s *p; p= (struct s *)malloc(10* sizeof(struct s)) if( p==NULL ) { perror("Fallo en malloc()"); exit(1); } else { p[3].a= 32; p[0].b= 1.45; p[9].a= 4365; }

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

void *realloc(void *ant, int tam)

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

Un ejemplo de paso de un entero por referencia a una funcin que lo incremente:


int x= 14; inc(&x); ... void inc(int *par) { (*par)++; }

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

Podremos asignar su direccin al puntero pt usando el nombre de la funcin sin parntesis:


pt= f;

y luego podramos llamar a la funcin de una de tres formas diferentes:


f( "pepe", 123.2, vec ); (*pt)( "pepe", 123.3, vec ); pt( "pepe", 123.3, vec );

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

{ double x, y; for( x=ini; x<=fin; x+=inc ) { y= fun(x); printf("%f > %f\n", x, y ); } }

Se puede, incluso, declarar un vector de punteros a funcin


double (*vec[3])(double)= { sin, prueba, log };

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

Potrebbero piacerti anche