Vous êtes sur la page 1sur 10

Programa de Formación de la Academia de Software Libre

Nivel: Desarrolladores en Software Libre


Módulo: Lenguaje de Aplicaciones

UNIDAD 4: Tipos de datos derivados

Objetivo terminal de la unidad

Aprender a utilizar los punteros y apuntadores como método de programación y solución


de problemas usando lenguaje C.

Temas

● Punteros o apuntador
● Declaración de punteros
● Operadores dirección (&) e indirección (*)
● Aritmética de punteros
● Punteros y arreglos
● Funciones de asignación dinámica, malloc() y free()

● Punteros o apuntador

Se conoce como puntero a una variable que contiene una dirección de memoria.
Normalmente, esa dirección es la posición de otra variable de memoria. Si una variable
contiene la dirección de otra variable, entonces se dice que la primera variable apunta a
la segunda.
Al una variable contener un puntero, se debe declarar como tal. Una declaración
de un puntero consiste en un tipo base, un * y el nombre de la variable. La forma
general es:tipo *nombre; donde tipo es cualquier tipo válido y nombre es el nombre de
la variable puntero. El tipo base del puntero define el tipo de variables a las que puede
apuntar.
Desde el punto de vista técnico, cualquier tipo de puntero puede apuntar a
cualquier dirección de la memoria, sin embargo, toda la aritmética de punteros se lleva
acabo en relación a sus tipos base, por lo que es importante declarar correctamente el
puntero.
Se cuenta con dos operadores especiales de punteros: & y *. El operador de
dirección (&) devuelve la dirección de memoria de su operando. El operador de
indirección (*) devuelve el contenido de la dirección apuntada por el operando.
Una vez se declara un puntero, pero antes de asignarle un valor, éste contiene un
valor desconocido; si en ese instante lo intenta utilizar probablemente no funcionará, no
sólo el programa sino también el sistema operativo. Se debe asignar por convenio el
valor nulo a un puntero que no se encuentre apuntando a ningún sitio, aunque ésto
tampoco es seguro.

● Declaración de punteros

Pág. 6 Curso: Lenguaje C Unidad 4: Tipo de datos


derivados
Programa de Formación de la Academia de Software Libre

Nivel: Desarrolladores en Software Libre


Módulo: Lenguaje de Aplicaciones

Al declarar una variable, como es bien sabido, lo que realmente se hace es


reservar espacio en memoria para dicha variable, el tamaño del espacio reservado
dependerá del tipo de la variable. Por lo tanto, al declarar un puntero lo que se hace es
reservar espacio para almacenar una dirección de memoria, no el contenido de la
variable.
La imagen mostrada a continuación muestra tres variables, de tipo char, int y double, y
un puntero hacia cada una de ellas:

Figura 1: Punteros a variables char, int y double. Tomado de:


http://laurel.datsi.fi.upm.es/~rpons/personal/trabajos/curso_c/node99.html

La variable de tipo char se encuentra almacenada en la dirección de memoria 100.


El contenido de dicha posición, es decir, el contenido de la variable, es el caracter ``a''.
Por otra parte, el puntero p1 se encuentra en la dirección 210, y su valor es 100. Tal y
como se puede apreciar, el contenido de la variable p1 (puntero) es la dirección de la
variable char.
De la misma forma, mientras que el tamaño que ocupan en memoria las variables
es diferente, un char ocupa un byte, un double ocupa 8 bytes, el tamaño que ocupan las
variables tipo puntero es siempre el mismo: 4 bytes. Esto es lógico ya que las direcciones
de memoria siempre ocupan el mismo tamaño.

Asignación de punteros

Pág. 6 Curso: Lenguaje C Unidad 4: Tipo de datos


derivados
Programa de Formación de la Academia de Software Libre

Nivel: Desarrolladores en Software Libre


Módulo: Lenguaje de Aplicaciones

Un puntero puede ser utilizado a la derecha de una declaración de asignación para


asignar su valor a otro puntero, como para cualquier otra variable. Por ejemplo:

int x;
int *p1,*p2;
p1=&x;
p2=p1;
Tanto p1 como p2 apuntan a x.

● Operadores dirección (&) e indicadores (*)

El lenguaje C dispone del operador dirección (&), como ya se ha mencionado, el


cual permite encontrar la dirección de la variable a la que se aplica. Un puntero es una
verdadera variable, y por lo tanto puede cambiar de valor, es decir, puede cambiar la
variable a la que apunta. Para acceder al valor depositado en la zona de memoria a la
que apunta un puntero se debe utilizar el operador indirección (*). Por ejemplo, tómese
en cuenta las siguientes declaraciones y sentencias,

int i, j, *p; // p es un puntero a int


p = &i; // p contiene la dirección de i
*p = 10; // i toma el valor 10
p = &j; // p contiene ahora la dirección de j
*p = -2; // j toma el valor -2

Como las constantes y las expresiones no tienen dirección, no se les puede aplicar
el operador (&), tampoco se puede cambiar la dirección de una variable. Los valores
posibles para un puntero son las direcciones posibles de memoria. Un puntero puede
tener valor 0, la cual es equivalente a la constante simbólica predefinida NULL. No se
puede asignar una dirección absoluta de forma directa, se debe hacer un casting. Las
siguientes sentencias son ilegales:

p = &34; // las constantes no tienen dirección


p = &(i+1); // las expresiones no tienen dirección
&i = p; // las direcciones no se pueden cambiar
p = 17654; // habría que escribir p = (int *)17654;

Para imprimir punteros con la función printf() se deben utilizar los formatos %u y
%p.

Pág. 6 Curso: Lenguaje C Unidad 4: Tipo de datos


derivados
Programa de Formación de la Academia de Software Libre

Nivel: Desarrolladores en Software Libre


Módulo: Lenguaje de Aplicaciones

No se puede realizar asignaciones directas (sin casting) entre punteros que apuntan
a distintos tipos de variables. Sin embargo, existe un tipo indefinido de punteros (void *,
o punteros a void), los cuales pueden ser asignados, y a los que puede asignárseles
cualquier tipo de puntero. Por ejemplo:

int *p;
double *q;
void *r;
p = q; // ilegal
p = (int *)q; // legal
p = r = q; // legal

● Aritmética de punteros

De auerdo con lo visto, los punteros son unas variables especiales, ya que guardan
información, no sólo sobre la dirección a la que apuntan, sino también del tipo de
variable almacenado en dicha dirección. Esto implica que no se permitirán las
operaciones que no tienen sentido con direcciones de variables, como multiplicar o
dividir, pero sí otras como sumar o restar. Además, estas operaciones se realizan de un
modo correcto, pero que no es el ordinario. Así, la sentencia:

p = p+1;

hace que p apunte a la dirección siguiente de la que apuntaba, teniendo en cuenta el tipo
de dato. Por ejemplo, si el valor apuntado por p es short int y ocupa 2 bytes, el sumar 1
a p implica añadir 2 bytes a la dirección que contiene, mientras que si p apunta a un
double, sumarle 1 implica añadirle 8 bytes.

La diferencia de punteros al mismo tipo de variable también tiene sentido. El


resultado es la distancia entre las direcciones de las variables apuntadas por éstos, no en
bytes sino en datos del mismo tipo. Las siguientes expresiones tienen pleno sentido en
C:

p = p + 1;
p = p + i;
p += 1;
p++;
Pág. 6 Curso: Lenguaje C Unidad 4: Tipo de datos
derivados
Programa de Formación de la Academia de Software Libre

Nivel: Desarrolladores en Software Libre


Módulo: Lenguaje de Aplicaciones

Para resumir, sólo dos operaciones aritméticas pueden ser identificadas, las cuales
pueden utilizarse con punteros: la suma y la resta.

Al incrementar un puntero, se apunta a la posición de memoria del siguiente


elemento de su tipo base. Cada vez que se decrementa, se apunta a la posición del
elemento anterior. Con punteros a caracteres parece una aritmética normal, sin
embargo,el resto de los punteros aumentan o decrecen la longitud del tipo de datos a los
que apuntan.

Por ejemplo, se asumirá que los enteros son de dos bytes de longitud y p1 es un
puntero a entero con valor actual 2000. En consecuencia, después de la expresión p1+
+; p1 contiene el valor 2002, no 2001.
No se pueden realizar otras operaciones aritméticas sobre los punteros más allá de
la suma y resta de un puntero y un entero. En particular, no se pueden multiplicar o
dividir punteros y no se puede sumar o restar el tipo float o el tipo double a los punteros.
El siguiente ejemplo ilustra la aritmética de punteros, la tabla 4.1 muestra las
direcciones de memoria asignadas a las variables del ejemplo:

Variable Dirección de memoria


a 00FA:0000
b 00FA:0002
c 00FA:0004
p1 00FA:0006
p2 00FA:000A
p 00FA:000E

Tabla 4.1 tabla de direcciones

void main(void) {
int a, b, c;
int *p1, *p2;
void *p;
p1 = &a; // Paso 1. La dirección de a es asignada a p1
*p1 = 1; // Paso 2. p1 (a) es igual a 1. Equivale a a = 1;
p2 = &b; // Paso 3. La dirección de b es asignada a p2
Pág. 6 Curso: Lenguaje C Unidad 4: Tipo de datos
derivados
Programa de Formación de la Academia de Software Libre

Nivel: Desarrolladores en Software Libre


Módulo: Lenguaje de Aplicaciones

*p2 = 2; // Paso 4. p2 (b) es igual a 2. Equivale a b = 2;


p1 = p2; // Paso 5. El valor del p1 = p2
*p1 = 0; // Paso 6. b = 0
p2 = &c; // Paso 7. La dirección de c es asignada a p2
*p2 = 3; // Paso 8. c = 3
printf("%d %d %d\n", a, b, c); // Paso 9. ¿Qué se imprime?
p = &p1; // Paso 10. p contiene la dirección de p1
*p = p2; // Paso 11. p1= p2;
*p1 = 1; // Paso 12. c=1
printf("%d %d %d\n", a, b, c); // Paso 13. ¿Qué se imprime?
}

Suponiendo que en el momento de comenzar la ejecución, las direcciones de


memoria de las distintas variables son las mostradas en la tabla 4.1. La dirección de
memoria se encuentra en hexadecimal, con el segmento y el offset separados por dos
puntos (:); basta prestar atención al segundo de estos números, esto es, al offset.

En la Tabla 4.2 se muestra los valores de las variables en la ejecución del


programa paso a paso. En negrita y cursiva se muestran los cambios entre cada uno. Es
importante analizar y entender los cambios de valor.

Paso a b c p1 p2 p
00FA:0000 00FA:0002 00FA:0004 00FA:0006 00FA:000A 00FA:000E
1 00FA:0000
2 1 00FA:0000
3 1 00FA:0000 00FA:0002
4 1 00FA:0000 00FA:0002
5 1 2 00FA:0002 00FA:0002
6 1 2 00FA:0002 00FA:0002
7 1 0 00FA:0002 00FA:0004
8 1 0 3 00FA:0002 00FA:0004
9 1 0 3 00FA:0002 00FA:0004
10 1 0 3 00FA:0002 00FA:0004 00FA:0006
11 1 0 3 00FA:0004 00FA:0004 00FA:0006
12 1 0 1 00FA:0004 00FA:0004 00FA:0006

Pág. 6 Curso: Lenguaje C Unidad 4: Tipo de datos


derivados
Programa de Formación de la Academia de Software Libre

Nivel: Desarrolladores en Software Libre


Módulo: Lenguaje de Aplicaciones

13 1 0 1 00FA:0004 00FA:0004 00FA:0006


Tabla 4.2. Ejecución paso a paso de un ejemplo con punteros.

● Punteros y arraglos

Existe una estrecha relación entre los punteros y los arreglos. Tómese en cuenta el
siguiente fragmento:

char cad[80], *p1;


p1=cad;

En éste, p1 ha sido asignado a la dirección del primer elemento del arreglo cad.
Para acceder al quinto elemento de cad se escribe cad[4] o *(p1+4).
Se devolverá la dirección de comienzo del arreglo, que es el primer elemento, si
aparece un nombre de arreglo sin índice. El compilador traduce la notación de arreglos a
notación de punteros.
Esto es igual a decir, que al crear un arreglo se genera un puntero, una constante
de puntero en realidad, con el mismo nombre que apunta a la dirección del primer
elemento del arreglo.
Los punteros pueden ser estructurados en arreglos como cualquier otro tipo de
datos. La declaración, por ejemplo, para un arreglo de punteros a enteros de tamaño 10
es:

int *x[10];

Para asignar la dirección de una variable entera llamada var al tercer elemento del
arreglo de punteros se escribe:

x[2]=&var;

Se puede encontrar el valor de var de la forma: var de la forma: *x[2]; si se


quiere pasar un arreglo de punteros a una función se puede utilizar el mismo método que
se utiliza para otros arreglos: llamar simplemente a la función con el nombre del arreglo
sin índices. Así se pasa el puntero que apunta al arreglo.
Pág. 6 Curso: Lenguaje C Unidad 4: Tipo de datos
derivados
Programa de Formación de la Academia de Software Libre

Nivel: Desarrolladores en Software Libre


Módulo: Lenguaje de Aplicaciones

No se pasa un puntero a enteros, sino un puntero a un arreglo de punteros a enteros.

Ejemplo de implementación con arreglos y punteros

Para ilustrar con un ejemplo, se desea que el usuario introduzca varios números
de teléfono y se almacenen en el arreglo teléfonos. Se desconoce cuántos números va a
introducir el usuario. Si se tuviese que implementar con un arreglo definido desde su
declaración, se tendría que asignar un tope a los números de teléfono que se pueden
introducir, por ejemplo 10:

int i;
int telefonos[10];

i=0;
while (usuario_introduce && (i<10))
{
telefonos[i] = numero_introducido;
i++;
}
numeros_introducidos = i;

Sin embargo, trabajando con punteros se puede preguntar al usuario cuántos


números quiere introducir:

int i, tamano;
int *telefonos; // o bien: int telefonos[]

telefonos = NULL;
/* es muy recomendable inicializar el valor de un puntero a NULL.
* Así, si se nos olvida pedir memoria para él, el programa fallará siempre.
* Por el contrario, si no se inicializa, el programa fallará
* algunas veces sí y otras no. */

tamano = preguntar_tamano();
telefonos = malloc (tamano);

for (i=0;i<tamano;i++)
telefonos[i] = numero_introducido;

O también se puede variar el tamaño del arreglo de forma dinámica:

Pág. 6 Curso: Lenguaje C Unidad 4: Tipo de datos


derivados
Programa de Formación de la Academia de Software Libre

Nivel: Desarrolladores en Software Libre


Módulo: Lenguaje de Aplicaciones

int i, tamano, nuevo_tamano;


int *telefonos; // o bien: int telefonos[]

telefonos = NULL;
i=0;
while (usuario_introduce)
{
nuevo_tamano = sizeof (int) * (i+1);
telefonos = realloc (telefonos,nuevo_tamano);
telefonos[i] = numero_introducido;
i++;
}
tamano=i;

● Funciones de asignación dinámica, malloc() y free()

Los punteros proporcionan el soporte necesario para el potente sistema de


asignación dinámica de memoria de C. La asignación dinámica es la forma en la que un
programa puede obtener memoria mientras se está ejecutando.
A las variables globales se les asigna memoria en tiempo de compilación y las
locales usan la pila. Sin embargo, durante la ejecución no se pueden añadir variables
globales o locales, pero existen ocasiones en las que un programa necesita utilizar
cantidades de memoria variables.
El centro del sistema de asignación dinámica se compone de las funciones,
existentes en la biblioteca stdlib.h malloc(), que asigna memoria; y free() que la
devuelve.
El prototipo de la función malloc() es: stdlib.h) malloc(), que asigna memoria; y
free() que la devuelve.void *malloc(size_t número de bytes);
Tras una llamada exitosa, el malloc() devuelve un puntero, el primer byte de
memoria dispuesta. Si no hay suficiente memoria libre para satisfacer la petición de
malloc(), se da un fallo de asignación y devuelve un nulo. El fragmento de código
presentado a continuación asigna 1000 bytes de memoria:

char *p;
p = (char *) malloc(1000);

p apunta al primero de los 1000 bytes de la memoria libre, después de la


asignación. El ejemplo presentado a continuación dispone espacio para 50 enteros.
Pág. 6 Curso: Lenguaje C Unidad 4: Tipo de datos
derivados
Programa de Formación de la Academia de Software Libre

Nivel: Desarrolladores en Software Libre


Módulo: Lenguaje de Aplicaciones

Nótese el uso de sizeof para asegurar la portabilidad: p apunta al primero de los 1000
bytes de la memoria libre. El siguiente ejemplo dispone espacio para 50 enteros. Nótese
también el uso de sizeof para asegurar la portabilidad:

int *p;
p= (int *) malloc(50*sizeof(int));

La función free() es la opuesta de malloc() porque devuelve al sistema la memoria


previamente asignada. Una vez que la memoria ha sido liberada puede ser reutilizada en
una posterior llamada a malloc(). El prototipo de la función free() es:

void free (void *p);


free(p);

Pág. 6 Curso: Lenguaje C Unidad 4: Tipo de datos


derivados

Vous aimerez peut-être aussi