ASIGNACIÓN DINÁMICA - TABLA DE FUNCIONES PARA EL MANEJO DE MEMORIA DINÁMICA







 Tabla de funciones para el manejo de memoria.
Estas funciones tienen sus encabezados en el archivo de encabezado stdlib.h . Por tanto, será preciso añadir la directriz: #include<stdlib.h> en los programas que hagan uso de ellas. En todo caso, el compilador señalará un error de no hacerse así.

 calloc().

void * calloc(size_t num_elementos, size_t tamaño_de_un_elemento);

Esta función proporciona el puntero de un bloque reservado por el sistema operativo en el cúmulo, si es posible, o bien el valor NULL, si no es posible (por falta de memoria o por fragmentación del cúmulo). El bloque tiene longitud suficiente para almacenar en él num_elementos variables del tamaño dado por tamaño_de_un_elemento . Si consideramos elementos de un determinado Tipo_base y se desea crear una lista formada por NUM_ELEMENTOS elementos de ese tipo base, la llamada a calloc() necesaria para reservar el espacio suficiente puede ser similar a la que aparece en el siguiente código: Tipo_base * p; p = calloc(NUM_ELEMENTOS, sizeof(Tipo_base)); Esta llamada produce un puntero de tipo void * , que en principio debe ser compatible con cualquier tipo de puntero. De no ser así, procederíamos a refundir la variable, empleando una llamada de la forma: p = (Tipo_base *)calloc(NUM_ELEMENTOS, sizeof(Tipo_base)); que produciría el resultado deseado. Si no es posible reservar espacio suficiente, la función calloc() proporciona como resultado el puntero NULL.

 malloc().

void *malloc(size_t tamaño_en_bytes)

Esta función sirve también para reservar espacio en memoria. Su único argumento sirve para indicar al sistema operativo el número de bytes de que consta el bloque deseado para que éste, si es posible, los reserve y proporcione un puntero del bloque. Si no es posible, la función proporcionará el valor NULL. Entonces la forma de reservar espacio para una lista de variables de algún Tipo_base que esté formada, como en el caso anterior, por NUM_ELEMENTOS variables, será similar a la siguiente: Tipo_base * p; p = malloc(NUM_ELEMENTOS*sizeof(Tipo_base)); y producirá el mismo resultado que la llamada anterior a calloc() : p apuntará a un bloque de las dimensiones adecuadas para una lista formada por NUM_ELEMENTOS variables del Tipo_base indicado. Tanto si se emplea calloc() como si se emplea malloc() , el resultado es (si todo va bien) la dirección de un bloque, que almacenamos en un puntero. Nuestro único contacto con la variable creada de esta manera es, precisamente, ése puntero. La variable carece de nombre y si perdemos la dirección en que comienza, será imposible acceder a ella. Esto es malo por dos razones:

realloc()

void * realloc(void *puntero, size_t tamaño)

Esta función tiene una misión interesante: ampliar o reducir un bloque de memoria dinámica creado previamente. De este modo se puede aumentar o reducir el número de elementos de una tabla, sin más que aumentar o reducir el número de bytes reservados para el bloque que la contiene. El puntero suministrado como argumento señala el bloque cuyas dimensiones se quieran modificar, y el segundo argumento es el nuevo número de bytes que deba ocupar ese bloque al finalizar la actuación de realloc() .
Nota.- Las reducciones de tamaño siempre son posibles, pero lo verdaderamente interesante (las ampliaciones del bloque) está condicionado por la disponibilidad de espacio contiguo suficiente en el cúmulo. Evidentemente, realloc() no puede hacer "magia": si no existe en el cúmulo un bloque libre de dimensiones tan grandes como el nuevo tamaño indicado, no será posible realizar la operación. Pero si se dispone de espacio suficiente, realloc() copiará al principio del bloque nuevo el contenido del bloque viejo, y liberará la memoria ocupada por el bloque viejo, proporcionando siempre (en ampliación o reducción) la dirección del nuevo bloque como resultado.

free()

void free(void * ptro);

Esta función es la encargada de liberar los bloques de memoria que, habiendo sido reservados mediante calloc() , malloc() o realloc() hayan dejado de ser útiles, pudiendo ser devueltos a la lista de bloques disponibles que mantiene el sistema operativo. Los bloques así liberados servirán para reservar otros bloques, de tamaños iguales o distintos al del bloque devuelto
El único argumento de esta función, ptro , denota el puntero del bloque que se quiera liberar. Este puntero no puede ser NULL ni tampoco puede señalar a un bloque que ya haya sido liberado (mediante una llamada previa a free() ). Todo intento de realizar una de estas operaciones tendrá graves consecuencias.

 memcpy().

void memcpy(void * destino, const void * origen, size_t num_bytes_copiados);

Esta interesante función permite realizar copias rápidas de bloques de memoria. El puntero origen denota la dirección en que comienza el bloque que sirve como fuente de información; destino denota la dirección en que comienza el bloque en el cual se copiará la información y num_bytes_copiados denota el número exacto de bytes que, comenzando en origen se copiarán a partir de la dirección señalada por destino . Una buena regla mnemotécnica es pensar que la sintaxis de esta función es memcpy(dónde, qué, cuánto);
Como nota final diremos que memcpy() proporciona como resultado el puntero del objeto de destino .

Ejemplo
Vamos a construir un programa que crea una lista, una tabla y un tarugo (una matriz tridimensional) empleando memoria dinámica.
/*
 Este programa muestra el uso de calloc(), malloc() y free()
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define Tipo_base int
/*
 Constantes
*/
#define NUM_ELEMENTOS 10
#define NUM_CAPAS 2
#define NUM_FILAS 3
#define NUM_COLS 4
/*
 Primero declaramos un puntero de Tipo_base,
 que servirá como puntero de lista porque
 p[i] equivale a *(p+i)
*/
Tipo_base *p1 ;
/*
 Ahora declaramos un puntero de fila,
 que servirá para recorrer una tabla
 empleándolo con dos índices.
*/
Tipo_base (*p2)[NUM_COLS];
/*
 Por último declaramos un puntero de tabla,
 que servirá para recorrer tarugos
 empleando hasta tres índices.
*/
Tipo_base (*p3)[NUM_FILAS][NUM_COLS];
 
int main(void)
{
 int i, j, k;
 printf ("Asignación dinámica de memoria\n\n");
/*
 Vamos a reservar espacio para una lista formada por
 NUM_ELEMENTOS variables de Tipo_base.
*/
  p1 = malloc(NUM_ELEMENTOS*sizeof(Tipo_base)); 
/* Evidentemente, esto sería equivalente a poner
 p1 = calloc(NUM_ELEMENTOS, sizeof(Tipo_base));
*/
/*
 Vamos a reservar espacio para una tabla formada por
 NUM_FILAS*NUM_COLS variables de Tipo_base.
*/
  p2 = malloc(NUM_FILAS*NUM_COLS*sizeof(Tipo_base)); 
/* Evidentemente, esto sería equivalente a poner
 p2 = calloc(NUM_FILAS*NUM_COLS, sizeof(Tipo_base));
*/
 p3 = malloc(NUM_CAPAS*NUM_FILAS*NUM_COLS*sizeof(Tipo_base)); 
/* Evidentemente, esto sería equivalente a poner
 p3 = calloc(NUM_CAPAS*NUM_FILAS*NUM_COLS, sizeof(Tipo_base));
*/
/*
 Ahora damos valores a la lista monodimensional.
*/
 for(i=0;i<NUM_ELEMENTOS;i++) p1[i] = i;
/*
 Y a la tabla (con dos índices).
*/
 for(i=0;i<NUM_FILAS;i++)
  for(j=0;j<NUM_COLS;j++)
   p2[i][j] = 10*i+j;
/*
 Y al tarugo (con tres índices).
*/
 for(i=0;i<NUM_CAPAS;i++)
  for(j=0;j<NUM_FILAS;j++)
   for(k=0;k<NUM_COLS;k++)
    p3[i][j][k] = 100*i+10*j+k;

/*
 Comprobemos que realmente se han almacenado esos valores.
*/
 printf("p1[9] = %d\n",p1[9]);
 printf("p2[2][3] = %d\n",p2[2][3]);
 printf("p3[1][2][3] = %d\n", p3[1][2][3]);

/*
 Por último, liberamos la memoria reservada.
*/
 free(p1);
 free(p2);
 free(p3);
 printf ("\nTerminación normal del programa\n");
 
 return 0;
}
 
/*
 Resultado
 Asignación dinámica de memoria
 
 p1[9] = 9
 p2[2][3] = 23
 p3[1][2][3] = 123
 
 Terminación normal del programa
*/



Ejercicios propuestos



  1. Ejercicio 1102r01 .-La función malloc_size() proporciona el tamaño del bloque proporcionado como argumento. Reservar dinámicamente bloques de varios tamaño y verificar que malloc_size() proporciona resultados correctos. ¿Cuánto ocupa una estructura de tipo FILE ? ¿Qué dice sizeof() ?

  2. Ejercicio 1102r02.- Construir una pila basada en una lista lineal dinámica. La pila contará con una cierta extensión inicial (digamos 3 elementos) e incrementará su tamaño en 10 elementos cuando sea necesario. El programa debe mostrar siempre el número de elementos libres.

  3. Ejercicio 1102r03 .-Construir una cola basada en una lista lineal dinámica. La cola contará con una cierta extensión inicial (digamos 3 elementos) e incrementará su tamaño en 10 elementos cuando sea necesario. El programa debe mostrar siempre el número de elementos libres.

  4. Ejercicio 1102r04 .-Construir una estructura, llamada Vector, que tenga el comportamiento de una lista lineal infinita. El tipo base de esta lista será void *. Se pide dotar a esta estructura de las funciones necesarias para añadir elementos (sin especificar un índice), o para proporcionar la dirección del i-ésimo elemento de un Vector (si existe). La implementación estará basada en una lista lineal.

  5. Ejercicio 1102r05 .-Se dispone de un fichero de texto. Construir una función capaz de proporcionar como resultado la dirección de una estructura con campos adecuados para contener todas las líneas del fichero, indicando su número. Esta función debe construirse de tal modo que sirva para cargar archivos encolumnados, delimitados y de formato libre indistintamente.

  6. Ejercicio 1102r06 .-Un usuario necesita sumar dos tablas MxN, pero no conoce sus dimensiones. Construir un programa que solicite ésas dimensiones, construya dinámicamente las tablas, las cargue a partir del teclado y muestre el producto de ambas tablas en pantalla.

  7. Ejercicio 1102r07 .-Un usuario dispone de un archivo binario en el que está almacenada una tabla de float , de dimensiones MxN. Estas dimensiones se han almacenado en el archivo, que por tanto tiene formato binario inhomogéneo (un primer registro de dimensiones) y después la información en sí. Se pide construir un programa capaz de leer, mostrar, modificar y escribir tablas almacenadas de esta manera.

  8. Ejercicio 1102r08 .-Diseñar e implementar las estructuras de datos necesarias para tratar matrices cuadradas de cualquier dimensión. El programa leerá y guardará información en disco, y será capaz de efectuar sumas, restas y productos matriciales, almacenando el problema completo (con sus resultados).