Está en la página 1de 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 m00 m01 m02 m03


m1
m2 m10 m11 m12 m13

m20 m21 m22 m23

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 *cad, float x, float
v[]);

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

También podría gustarte