You are on page 1of 10

Versión 3.

800x600 mínimo
En esta lección:

¿Qué es la
asignación dinámica?
----- ¿Qué es la asignación dinámica?
Un array de
punteros
----- Hasta este punto del tutorial de C las variables que se han utilizado en los programas son de tipo
Una lista enlazada
----- estáticas. (Algunas de ellas han sido automáticas y fueron asignadas dinámicamente para Usted
por el sistema pero esta operación pasó inadvertida para Usted). En este capítulo estudiaremos
Otras secciones: algunas variables asignadas dinámicamente, éstas son variables que no existen cuando se carga
el programa pero se crean dinámicamente cuando son necesarias al correr el programa. Es
Conceptos básicos
----- posible, utilizando éstas técnicas crear tantas variables como sea necesario, utilizarlas y
Programando en C removerlas de su espacio en memoria para que sea utilizado por otras variables, como es
-----
Programando en C+ costumbre, un ejemplo habla bién del concepto:
+
-----
Programando
Windows 9x. #include <stdio.h>
----- #include <string.h>
Teoría electrónica #include <stdlib.h>
-----
Circuitos electrónicos
----- struct animal
Actividades {
adicionales char nombre[25];
-----
Hipervínculos
char raza[25];
----- int edad;
}
*mascota1, *mascota2, *mascota3;
Contácteme:

Dudas y comentarios int main()


----- {
mascota1 = (struct animal *)malloc(sizeof(struct animal));
/* Es un error no checar la asignacion, consulte el texto. */
/* Checaremos la asignacion en el siguiente programa. */
strcpy(mascota1->nombre, "General");
strcpy(mascota1->raza, "Colicondela");
mascota1->edad = 1;

/* mascota2 ahora señala a la construccion de arriba */


mascota2 = mascota1;
mascota1 = (struct animal *)malloc(sizeof(struct animal));
strcpy(mascota1->nombre, "Francisco");
strcpy(mascota1->raza, "Labrador Cobrador");
mascota1->edad = 3;

mascota3 = (struct animal *)malloc(sizeof(struct animal));


strcpy(mascota3->nombre, "Cristal");
strcpy(mascota3->raza, "Pastor Aleman");
mascota3->edad = 4;

/* Desplegamos los datos */


printf("%s es un %s, y su edad es de %d.\n",
mascota1->nombre, mascota1->raza, mascota1->edad);
printf("%s es un %s, y su edad es de %d.\n",
mascota2->nombre, mascota2->raza, mascota2->edad);
printf("%s es un %s, y su edad es de %d.\n",
mascota3->nombre, mascota3->raza, mascota3->edad);

/* mascota1 señala a la misma estructura que mascota3 */


mascota1 = mascota3;
free(mascota3); /* Esto libera una estructura */
free(mascota2); /* Esto libera otra estructura*/
/* free(mascota1); esto no se puede hacer, consulte el texto */

return 0;
}

Empezamos definiendo una estructura llamada animal con algunos campos en relación a
unos perros, no definimos ninguna variable de éste tipo sólo tres punteros, si Usted busca
en el resto del código del programa no encontrará ninguna variable definida por lo que no
tenemos en donde almacenar datos, todo lo que tenemos para trabajar son tres punteros,
cada uno de los cuales es capaz de señalar a variables de la estructura definida llamada
animal. Para hacer cualquier cosa con el programa necesitamos algunas variables por lo
que las crearemos dinámicamente

Creación dinámica de variables


El enunciado en la línea 15 asigna algo al puntero mascota1 creará una estructura
dinámica conteniendo tres variables, el corazón del enunciado es la función malloc ( )
gravada en el centro del enunciado. Esta es una función para asignar memoria que requiere
el resto del código de la línea para cumplir su objetivo. La función malloc ( ), por defecto,
asignará una parte de memoria en una pila (heap) de "n" caracteres de longitud y será de
tipo char. La "n" debe especificarse como el único argumento a la función, discutiremos "n"
en breve, pero primero necesitamos definir la pila (heap).

¿Qué es la pila (heap)?


La pila (heap) es una área predefinida de memoria que puede ser accesada por los
programas para almacenar datos y variables, éstos son asignados a la pila (heap) por el
sistema cuando se realizan llamadas a malloc ( ). El sistema mantiene un registro del lugar
donde los datos son almacenados, los datos y las variables pueden ser desasignadas al
gusto dejando "agujeros" en la pila, el sistema sabe la ubicación de los agujeros y los puede
utilizar para almacenamiento adicional de datos con llamadas adicionales a malloc ( ), la
estructura de la pila (heap) es por tanto una entidad en constante cambio, dinámica.

Espero que la breve explicación de la pila y la asignación dinámica sea suficiente para
entender lo que estamos haciendo con la función malloc ( ). Simplemente le solicita al
sistema un bloque de memoria del tamaño especificado regresando un puntero que señala
al primer elemento del bloque, el único argumento en el paréntesis es el tamaño deseado
del bloque que en nuestro caso, deseamos un bloque que almacene una de las estructuras
que definimos al principio del programa. El operador sizeof es nuevo, al menos para
nosotros en éste curso, regresa el tamaño en bytes del argumento entre paréntesis, regresa
por lo tanto, el tamaño de la estructura llamada animal y ése número es utilizado como
parámetro en la llamada a malloc ( ), al completar ésta llamada tenemos un bloque
asignado en la memoria con el puntero llamado mascota1 señalando el principio de éste
bloque.

¿Qué hacer si malloc ( ) falla?


Si no hay suficiente memoria disponible para el bloque solicitado, malloc ( ) no retorna un
puntero válido, en su lugar retorna el valor de NULL. El valor retornado siempre debe ser
checado antes de intetar utilizarlo, en el ejemplo no realizamos prueba alguna por dos
razones: en primer lugar y más importante es el hecho de presentar los tópicos uno a la vez
para facilitar el estudio, en segundo, estamos solicitando una pequeña cantidad de memoria
por lo que nuestro programa no causa problema. Sin embargo tenga siempre en cuenta que
toda la memoria asignada dinámicamente debe ser cuidadosamente verificada.

¿Qué es el reparto (cast)?


Al principio de la llamada a la función malloc ( ) podemos observar el llamado reparto
(Traducción en éste tutorial del término en inglés "cast"). La función malloc ( ) retorna por
defecto un puntero de tipo void, como no se puede utilizar un puntero que señale a nada,
debe ser cambiado a otro tipo. Usted puede definir el tipo de puntero con la construcción
dada en el ejemplo, en éste caso deseamos un puntero que señale a una estructura de tipo
animal, así que se lo decimos al compilador con éste reparto. Aún en el caso de omitir el
reparto, la mayoría de los compiladores retornan un puntero correctamente dando un
mensaje de advertencia y produciendo un ejecutable de todas maneras, es mejor práctica
indicarle al compilador el reparto adecuado.

Utilizando la estructura asignada dinámicamente


En las lecciónes en estructuras y punteros vimos que si tenemos una estructura con un
puntero señalandola podemos tener acceso a cualquier variable dentro de la estructura, en
las líneas 18 a la 20 del programa asignamos algunos valores con propósito de ilustración,
observe que son similares a la asignación de variables estáticas. En la línea 22 asignamos el
valor de mascota1 a mascota2, esto no crea un nuevo dato, simplemente tenemos dos
punteros al mismo objeto. Como mascota2 señala a la estructura creada para mascota1,
ésta puede ser utilizada de nueva cuenta para asignar dinámicamente otra estructura, lo
mismo se puede decir para mascota2. A la nueva estructura le asignamos algunos valores
para ilustración en las líneas 24 a la 26. Finalmente asignamos datos a mascota3 de la
misma manera yá explicada y desplegamos los resultados en pantalla.

Desechando los datos asignados dinámicamente


Para desechar los datos asignados dinámicamente y liberar el espacio para su reutilización
utilizamos la función free ( ), para ésto simplemente llámela utilizando el puntero del
bloque asignado dinámicamente como único parámetro y el bloque es liberado. Para ilustrar
otro aspecto de la asignación dinámica y la liberación de espacio, he incluido una etapa
adicional en el programa, en la línea 41 se le asigna a mascota1 el valor de mascota3, al
hacer ésto, el bloque que señalaba a mascota1 se pierde ya que no existe puntero que
señale al bloque de memoria, por lo tanto no podemos referirlo, cambiarlo ó desasignarlo,
éste bloque de memoria, el cual es parte de la pila, se ha desperdiciado, ésto no debe
hacerse en un programa, lo mostramos a manera de ilustración. La primera llamada a la
función free ( ) remueve el bloque de datos señalado por mascota1 y mascota3 lo que
desasigna la estructura y libera el espacio de memoria para su uso posterior. La segunda
llamada a free ( ) remueve el bloque señalado por mascota2, en este punto del programa
hemos perdido todo acceso a los datos generados al principio del programa.

Volver al principio

Un array de punteros
Parece que la explicación de la asignación dinámica en el anterior ejemplo fué muy extensa, la
buena noticia es que se ha dicho prácticamente todo al respecto, claro está que aún falta por
aprender algunas técnicas en el uso de la asignación dinámica y esto es lo que haremos en los
siguientes ejemplos, empezando con el siguiente código:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct animal
{
char nombre[25];
char raza[25];
int edad;
}
*mascota[12], *puntero; /* Esto define 13 punteros, no variables */

int main()
{
int indice;

/* primero rellenar dinamicamente las estructuras con cualquier cosa */


for (indice = 0 ; indice < 12 ; indice++)
{
mascota[indice] = (struct animal *)malloc(sizeof(struct animal));
if (mascota[indice] == NULL)
{
printf("Falla en la asignacion de memoria.\n");
exit (EXIT_FAILURE);
}
strcpy(mascota[indice]->nombre, "Fido");
strcpy(mascota[indice]->raza, "Pastor para tacos");
mascota[indice]->edad = 4;
}

mascota[4]->edad = 12; /* estas lineas son para */


mascota[5]->edad = 15; /* poner algunos datos */
mascota[6]->edad = 10; /* en algunos de los campos */

/* Desplegamos los datos */


for (indice = 0 ; indice < 12 ; indice++)
{
puntero = mascota[indice];
printf("%s Es un %s, y tiene %d anos.\n",
puntero->nombre, puntero->raza, puntero->edad);
}

/* es buena practica de programacion liberar el espacio */


/* dinamicamente asignado en el programa */

for (indice = 0 ; indice < 12 ; indice++)


free(mascota[indice]);
return EXIT_SUCCESS;
}

Este programa es muy parecido al ejemplo anterior ya que se utiliza la misma estructura
pero en el presente caso definimos un array de punteros para ilustrar los mecanismos para
construir una base de datos grande utilizando un array de punteros en lugar de un puntero
sencillo a cada elemento, para mantener el ejemplo sencillo definimos 12 elementos en el
array y otro puntero adicional llamado puntero. El enunciado *mascota[12] es nuevo así
que es conveniente hacer algunos comentarios. Lo que hemos definido es un array de 12
punteros siendo el primero mascota[0] y el último mascota[11], como un array es en sí
un puntero, el nombre mascota es un puntero constante a otro puntero, ésto es válido en
C, no hay limites en cuanto al número de niveles señalados, así, por ejemplo, la expresión
int ****pt es legal ya que se trata de un puntero a un puntero a un puntero a un puntero
a una variable de tipo entero, se recomienda tal uso de los punteros hasta haber ganado
suficiente experiencia en su manejo.

Ahora que tenemos 12 punteros los cuales pueden usarse como cualquier otro, es sencillo
escribir un bucle para asignar dinámicamente un bloque de datos para cada puntero y
rellenar los respectivos campos con los datos deseados, en éste caso rellenamos los
espacios con datos de propósito ilustrativo pero bién podría tratarse de una base de datos,
de información proveniente de algún equipo de prueba de laboratorio ó cualquier otra
fuente de datos. Notará que en el ejemplo checamos cuidadosamente el valor retornado por
la función malloc ( ) para verificar que no contenga un valor de cero, si retorna un valor
NULL, desplegamos un mensaje indicandolo y terminando el programa, recuerde que en un
programa real no basta con terminar el programa, es aconsejable generar un reporte de
errores y darle al usuario la oportunidad de corregirlos y continuar el proceso iniciado antes
de cerrar el programa. Volviendo al análisis del código, en las líneas 31 a la 33 escogimos
algunos campos al azar para ilustrar el uso de enunciados sencillos y que los datos son
desplegados en el monitor. El puntero puntero se utiliza en el bucle para desplegar datos
sólo para ilustración, fácilmente se hubiera podido utilizar mascota[indice] en su lugar.
Finalmente los 12 bloques son liberados de la memoria antes de terminar el programa, el
resultado de la ejecución es el siguiente, tenga en cuenta que mi compilador no maneja la
letra eñe:

Volver al principio

Una lista enlazada


Finalmente llegamos a una de las técnicas de programación famosa por su intimidante nombre,
una lista enlazada dinámicamente, con un poco de tiempo invertido en el estudio del código se
verá que no es otra cosa que una técnica más de programación hecha de simples componentes y
que puede ser una poderosa herramienta, antes de presentar el código, considere ésta analogía: es
el día de su cumpleaños y su hermana le ha dado un presente, al abrirlo encuentra una nota que
dice "Busca en el armario", Usted vá al armario y encuentra otra nota que dice "Busca en la
televisión", debajo de la televisión encuentra otra nota, "...debajo de la cafetera", Usted continúa
la búsqueda y finalmente encuentra un par de calcetines debajo del plato del perro. Lo que Usted
hizo fué ejecutar una lista enlazada, el punto de inicio fué al abrir el regalo y terminó debajo del
plato de comida del perro, ahí terminó porque no había más notas. En nuestro programa haremos
lo mismo que su hermana lo forzó a hacer, pero más rápido y dejaremos una pequeña cantidad de
datos en cada punto intermedio del camino a recorrer. Tenemos además la posibilidad de
regresar al principio y recorrer la lista completa una y otra vez si así lo deseamos. El código de
nuestro programa es éste:
#include <stdio.h> /* Necesario para definir NULL */
#include <string.h>
#include <stdlib.h>

#define REGISTROS 6

struct animal
{
char nombre[25]; /* El nombre del animal */
char raza[25]; /* El tipo de animal */
int edad; /* La edad del animal */
struct animal *siguiente; /* señala a otra estructura de este tipo */
}
*puntero, *inicio, *previo; /* Se definen tres punteros */

int indice; /* Una variable global */

int main()
{
/* El primer registro siempre es un caso especial */
inicio = (struct animal *)malloc(sizeof(struct animal));
if (inicio == NULL)
{
printf("Falla en la asignacion de memoria\n");
exit (EXIT_FAILURE);
}

strcpy(inicio->nombre, "General");
strcpy(inicio->raza, "Pastor para tacos");
inicio->edad = 4;
inicio->siguiente = NULL;
previo = inicio;

/* Una vez iniciada la lista, se puede utilizar un bucle */


/* para rellenar los bloques */
for (indice = 0 ; indice < REGISTROS ; indice++)
{
puntero = (struct animal *)malloc(sizeof(struct animal));
if (puntero == NULL)
{
printf("Falla en la asignacion de memoria\n");
exit (EXIT_FAILURE);
}

strcpy(puntero->nombre, "Pancho");
strcpy(puntero->raza, "Labrador");
puntero->edad = 3;
/* señala al ultimo "siguiente" de este registro */
previo->siguiente = puntero;
puntero->siguiente = NULL; /* señala este "siguiente" a NULL */
previo = puntero; /* Este es ahora el registro previo */
}

/* Se despliegan los datos descritos */


puntero = inicio;
do
{
previo = puntero->siguiente;
printf("%s es un %s, y tiene %d anos de edad.\n",
puntero->nombre, puntero->raza, puntero->edad);
puntero = puntero->siguiente;
}
while (previo != NULL);

/* Es buena practica liberar el espacio utilizado */


puntero = inicio; /* primer bloque del grupo */
do
{
previo = puntero->siguiente; /* siguiente bloque */
free(puntero); /* libera el actual bloque */
puntero = previo; /* señala al siguiente */
}
while (previo != NULL); /* termina hasta que el siguiente sea NULL */

return EXIT_SUCCESS;
}

Este programa inicia de una manera similar a los ejemplos anteriores con la adición de una
declaración constante para uso posterior, la estructura es casi la misma excepto por la
adición de otro campo dentro de la estructura en la línea 12, se trata de un puntero a otra
estructura del mismo tipo y será utilizada para señalar a la siguiente estructura en orden,
de acuerdo a la analogía de arriba, éste puntero señalará a la siguiente nota, que a su vez
contendrá un puntero a la siguiente nota. Definimos tres punteros y una variable para
utilizarla como contador y con ésto estamos listos para empezar a utilizar la estructura
definida, una vez más con datos sin sentido, sólo para propósitos de ilustración.

El primer campo
Utilizando la función malloc ( ) solicitamos un bloque de almacenamiento en memoria (en
el heap) y lo rellenamos con datos, checando la correcta asignación. Al campo adicional, en
éste ejemplo llamado siguiente, se le asigna el valor de NULL, el cual es utilizado para
indicar que éste es el final de la lista, dejaremos el puntero llamado inicio señalando a ésta
estructura de tal manera que siempre señalará a la primera estructura de la lista.
Asignamos además a previo el valor de inicio por razones que pronto veremos. Tenga en
cuenta que los extremos de la lista enlazada serán siempre manejados de forma diferente a
los elementos centrales de la lista, en éste punto tenemos un elemento en nuestra lista
relleno de datos significativos.

Rellenando las estructuras adicionales


El siguiente grupo de enunciados de control y asignación están incluidos dentro de un bucle
for de tal manera que podemos construir nuestra lista una vez que ha sido definida. El
número de ciclos del bucle está determinado por la constante REGISTROS definida al
principio del programa, en cada ciclo, asignamos memoria, hacemos la prueba de
asignación, rellenamos los primeros tres campos y los punteros. En el último registro se le
dá a siguiente la dirección de éste nuevo registro porque el puntero llamado previo está
señalando al registro previo, entonces previo->siguiente está dando la dirección del
nuevo registro que hemos rellenado. En el nuevo registro se le asigna a siguiente el valor
de NULL y al puntero previo se le dá la dirección de éste nuevo registro porque la siguiente
vez que generemos un nuevo registro, éste será el previo en tal ocasión, aunque la
explicación está un poco confusa, estudiando el código le resultará fácil entender la
mecánica de asignación. Cuando termina el bucle tenemos una lista de 7 estructuras, 6
correspondientes a la ejecuión del bucle y la estructura que generamos antes de iniciar el
bucle, la lista tendrá las siguientes características:

 El puntero llamado inicio señala a la primera estructura de la lista.


 Cada estructura contiene un puntero a la siguiente estructura.
 La última estructura tiene un puntero que contiene el valor de NULL el cual puede
utilizarse para detectar el final de la lista.

Debe quedar claro que no es posible simplemente brincar a mitad de la lista y cambiar
algunos valores, la única manera de acceder, por ejemplo, a la tercera estructura es
iniciando por el principio y recorrer la lista una estructura a la vez, aunque parece un precio
alto por la conveniencia de colocar algo de datos fuera del area del programa, es de hecho
una buena forma de almacenar cierto tipo de datos.

Para desplegar los datos se utiliza un método similar al utilizado para generar los datos, los
punteros son inicializados y entonces utilizados para avanzar de registro en registro,
leyendo y desplegando cada registro a la vez. El despliegue termina cuando se encuentra un
NULL, así que el programa no necesita saber siquiera cuántos registros hay en la lista.
Finalmente borramos la lista completa para liberar espacio en memoria. Se debe tener
cuidado de no borrar el último registro antes de checar el valor NULL, ya que sería
imposible terminar el programa.

No es difícil ni tampoco trivial agregar elementos a mitad de una lista enlazada, es


necesario crear un nuevo registro, rellenarlo de datos y señalar su puntero al registro que le
precederá. Por ejemplo, si se desea agregar un nuevo registro entre los registros actuales
tercero y cuarto, se requiere que el nuevo registro apunte hacia el cuarto registro, y el
puntero del tercer registro debe señalar al registro recién creado. Agregar un nuevo registro
al principio y al final de la lista son casos especiales, como ejercicio se le deja al lector
considerar los pasos necesarios para agregar un nuevo registro en una lista doblemente
enlazada. El resultado de la ejecución del programa es éste:
Dos funciones más
Debemos mencionar un par de funciones, calloc ( ) y realloc ( ). La función calloc ( )
asigna un bloque de memoria inicializado todo en ceros lo cual puede ser útil en algunas
circunstancias. Generalmente asignamos memoria e inmediatamente la rellenamos con
datos así que es una pérdida de tiempo rellenar primero conceros el bloque de memoria y
después con los datos del programa, por ésta razón, la función calloc ( ) se utiliza muy
raramente. por otra parte, la función realloc ( ) se utiliza para cambiar el tamaño de un
bloque asignado de memoria, a su vez es raramente utilizada, aún por programadores
experimentados.

Volver al principio

© 1998, 1999 Virgilio Gómez Negrete, Derechos Reservados

You might also like