Memoria dinámica y su uso en C
Transcripción
Memoria dinámica y su uso en C
Tema 18: Memoria dinámica y su uso en C 1 M. en C. Edgardo Adrián Franco Martínez http://www.eafranco.com [email protected] @edfrancom edgardoadrianfrancom Estructuras de datos (Prof. Edgardo A. Franco) • En muchas ocasiones no es posible conocer de antemano la cantidad de variables necesarias para un programa computacional. • Existen aplicaciones que requieren de enormes cantidades de arreglos o datos por momentos breves en el funcionamiento del mismo, por lo que no es viable declarar de antemano a estas como variables, globales o locales de una función. Lo anterior implica emplear funciones de ANSI C que permiten reservar memoria de manera dinámica y ampliarla, reducirla o destruirla en tiempo de ejecución. • El manejo de memoria dinámica es la base del poder del lenguaje C y le da la capacidad de crear programas complejos que emplean grandes cantidades de memoria y los maneja de manera eficiente. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez Introducción 2 • Las variables globales y del programa principal (main) se almacenan en posiciones fijas de la memoria llamada memoria de datos. • Las variables locales se almacenan en el segmento de memoria llamada pila y existen solo cuando se hace una invocación a la función que las declaro. También se pueden declarar variables estáticas locales que también se almacenan en segmentos fijos de memoria o en la memoria de datos , sin embargo, también están disponibles en la función que las declaro. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • Todos los programas definen variables que pueden ser definidas como globales y locales. 3 • Sin embargo, no todas las veces es posible conocer el numero de variables con el que va a constar nuestro programa. C ofrece al desarrollador la opción de crear diferentes tipos de variables de forma dinámica, para crear tales variables se utilizan funciones como: malloc(), realloc(), calloc(), y free(). 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • Todas estas variables comparten una característica en común, se definen cuando se compila el programa. Esto significa que el compilador reserva espacio en memoria para almacenar los valores para estas variables. • Las regiones de memoria que reservan/liberan estas funciones son almacenadas en el montículo o heap. 4 (Main y código de funciones empleadas) Datos Memoria de proceso activo (Variables globales y declaradas dentro del main) Pila Pila (Stack) (Variables y datos de las funciones al estar en ejecución) 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez Código Montículo (Heap) (Variables dinámicas del programa) 5 • Por lo regular cuando se diseña un algoritmo, se debe conocer que elementos de entrada tendrá y cual será la salida, sin embargo, en algunas ocasiones no se sabe de forma exacta el numero de variables que requerirá nuestro algoritmo. • Por ejemplo, suponga que se van a registrar el numero de calificaciones de un conjunto de alumnos, para resolver este problema se podría utilizar una arreglo de calificaciones, sin embargo, si el numero de alumnos crece, nuestro programa ya no seria valido, puesto que no existen los recursos necesarios para almacenar todos los datos validos. Para resolver este problema es necesario recurrir al uso de apuntadores y a la asignación dinámica de memoria. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez Asignación dinámica de memoria 6 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • El espacio de memoria asignado a una variable generada de manera dinámica se crea durante la ejecución del programa (tiempo de ejecución), al contrario de las variables declaradas en código, que el espacio de memoria se les asigna en tiempo de compilación. • Una variable que es generada dinámicamente, se construye (por ejemplo con malloc) y se puede destruir en tiempo de ejecución (uso de free). 7 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • Recordatorio: Las instrucciones de un programa compilado se sitúan en segmentos de memoria denominado segmento de código (memoria de programa). Los datos del programa, tales como variables globales, se almacenan en un área denominado segmento de datos. Las variables locales y el respaldo de los registros de control del programa se sitúan en el segmento llamado pila. Y cuando un programa solicita memoria dinámica para una variable, se le asigna memoria del segmento denominado montículo o heap. 8 • malloc() es la forma más habitual de obtener bloques de memoria dinámica. La función genera o asigna un bloque de memoria que es el numero de bytes pasados como argumento. • malloc() devuelve un apuntador void* al bloque de memoria asignado, por lo tanto, hay que realizar un cast al tipo de apuntador requerido, para hacer buen uso de la memoria o de los datos que se lleguen a almacenar en dicho bloque de memoria. Nota: Todas las funciones de asignación dinámica de memoria se encuentran definidas en la biblioteca stdlib.h 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez Función malloc() 9 void* = malloc( size_t bytes); • Donde: • void*: es el apuntador que almacenará la referencia o apuntara al bloque de memoria generado. • bytes: es el tamaño en bytes del bloque de memoria que se va a solicitar. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • El prototipo de la función malloc() sería: 10 //otra forma de generar la memoria //dinámica seria: c = (char *)malloc( sizeof(char) ); entero = (int *)malloc( sizeof(int) ); flotante = (float *)malloc( sizeof(float) ); doble = (double *)malloc( sizeof(double) ); #include <stdio.h> #include <stdlib.h> int main( void ){ char *c; int *entero; float *flotante; double *doble; //Uso de malloc para generar variables sencillas c = (char *)malloc( 1 ); entero = (int *)malloc( 4 ); flotante = (float *)malloc( 4 ); doble = (double *)malloc( 8 ); *c = 'c'; *entero = 2378; *flotante = 128.89172378; *doble = 18947282.48263; printf( "valores: caracter %c, entero %d, flotante %f, doble %lf", *c, *entero, *flotante, *doble ); free( c ); free( entero ); free( flotante ); free( doble ); return 0; } 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • Por ejemplo: *c = 'a'; *entero = 10; *flotante = 3.89172378; *doble = 1.48263; printf( "valores: caracter %c, entero %d, flotante %f, doble %lf \n\n", *c, *entero, *flotante, *doble ); //Importantísimo liberar la memoria //cuando ya no es requerida free( c ); free( entero ); free( flotante ); free( doble ); 11 int *ptr; ptr = (int *)malloc( 10 * sizeof(int) ); • Al llamar a malloc() puede ser que no haya suficiente memoria disponible, entonces, malloc() devolverá NULL en la operación, por lo tanto, siempre es conveniente preguntar después de la operación si se asigno el bloque de memoria. int *ptr; ptr = (int *)malloc( 10 * sizeof(int) ); if( ptr == NULL){ printf( "No hay memoria disponible…\n" ); //no utilizar ptr return; //fin del programa o realizar la acción conveniente } //utilizar ptr 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • La función sizeof se utiliza con mucha frecuencia para referirnos al tamaño de memoria que se va a generar por las funciones de memoria dinámica. Por ejemplo, si se requiere reservar un bloque de memoria para un arreglo de 10 enteros: 12 int tam; int *ptr; printf( "Ingresa el tamaño del arreglo " ); scanf( "%d", &tam; ); ptr = (int *)malloc( tam * sizeof(int) ); Nota importante: Los apuntadores que se utilizan para hacer referencia al bloque de memoria asignado por malloc() son de tipo dinámico y NO es conveniente que dichos apuntadores apunten a otro lugar antes de liberar el bloque de memoria asignado ya que se estará perdiendo la referencia al bloque de memoria y no abra forma de recuperar la referencia ha esta, por lo tanto, la memoria no será liberada y hasta finalizar el programa. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • Si no se conoce el tamaño de memoria que se quiere reservar al momento de diseñar un algoritmo, dicho tamaño se puede solicitar al usuario y generar el bloque de memoria en tiempo de ejecución. Un pequeño ejemplo seria el siguiente: 13 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • Un arreglo bidimensional en realidad es un arreglo cuyos elementos son arreglos. Si el nombre de un apuntador unidimensional es un apuntador sencillo, entonces, el nombre de un arreglo bidimensional será un apuntador a apuntadores sencillos. Para asignar memoria a un arreglo multidimensional, se indica cada dimensión del arreglo al igual que se declara un arreglo unidimensional. 14 • Para generar al arreglo bidimensional utilizando memoria dinámica se hace en dos pasos: 1. Se solicita la memoria para crear un arreglo de apuntadores que van a apuntar a cada fila del arreglo. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez Uso de malloc() para generar un arreglo bidimensional int **arr2d = (int *)malloc( 10 * sizeof(int *) ); 1. Se solicita memoria para almacenar el numero de elementos que va a formar cada fila o arreglo unidimensional. arr2d[i] = (int*)malloc( elemFilas * sizeof( int ) ); 15 arr2d[0] arr2d[1] arr2d[2] arr2d[3] arr2d[4] arr2d[5] arr2d[6] Arreglos unidimensionales de tamaño n 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez arr2d Arreglo de apuntadores sencillos arr2d[7] arr2d[8] arr2d[9] Donde cada arr2d[i] es un apuntador sencillo que apunta a un arreglo unidimensional de tamaño n. 16 int main(){ int **arr2d, **aux; int tamFilas, elemFilas,i,j; int tam; do{ system( "cls" ); printf( "Ingresa el numero de filas: " ); scanf( "%d", &tamFilas ); }while( !(tamFilas > 0) ); arr2d = (int**)malloc( tamFilas * sizeof( int* ) ); aux = arr2d; //para asignar el tamaño de cada una de las filas o arreglos //unidimensionales for( i = 0; i < tamFilas; i++ ){ do{ printf( "Ingresa un valor valido de elementos de una fila: " ); scanf( "%d", &elemFilas ); }while( !(elemFilas > 0) ); //asignar valores a cada arreglo arr2D[i] srand( time(NULL) ); for( j=0; j<elemFilas; j++ ){ arr2d[i][j] = rand()%100; } //imprimir los valores for( j=0; j<elemFilas; j++ ){ printf( "%d ", arr2d[i][j] ); } printf( "\n"); } //liberar la memoria for(i=0; i<tamFilas; i++ ){ free( arr2d[i] ); } free( arr2d ); return 0; //dos formas para generar los arreglos unidimensional //de elementos //1.arr2d[i] = (int*)malloc( elemFilas * sizeof( int ) ); 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez #include <stdio.h> #include <stdlib.h> } //2./* *aux = (int*)malloc( elemFilas * sizeof( int ) ); aux++;*/ 17 • Cuando se termina de utilizar un bloque de memoria previamente asignado por cualquier función de asignación dinámica de memoria, se debe liberar el espacio de memoria y dejarlo disponible para otros procesos, esto se realiza utilizando la función free(). El prototipo la función free es: 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez Uso de free() para liberar la memoria asignada dinámicamente void free(void *ptr); • Donde: *ptr es el apuntador que hace referencia al bloque de memoria asignado, si ptr es NULL entonces free no hace nada. • Si embargo, si ptr es un apuntador mal referenciado, el uso de free probablemente destruya el mecanismo de gestión de memoria y provocará un fallo en la aplicación. 18 • calloc() es otra función que permite obtener memoria dinámica. Al igual que malloc() devuelve un apuntador void* que hace referencia al bloque de memoria generado o NULL si no existe memoria suficiente para generar el bloque solicitado, por tal motivo, también es necesario realizar un cast a un apuntador valido para manejar los datos que se van a almacenar en el bloque de memoria asignado. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez Función calloc() 19 void* calloc( size_t n, size_t t ); • Donde: • n: es el numero de datos que se van a almacenar en la memoria. • t: es el tamaño de cada elemento, es decir, el tamaño del tipo de dato. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • El prototipo de la función calloc() es: 20 • Forma de uso: puntero = (tipo*) calloc( numElem, tamElem ); Donde: puntero: es un apuntador valido que hace referencia al bloque de memoria generado. tipo *: es el cast al un apuntador valido. numElem: es el numero de elementos que se van a almacenar en el bloque de memoria. tamElem: es el tamaño del tipo de dato que se va a almacenar en la memoria. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez Función calloc() calloc() una cantidad de memoria igual a numElem * tamElem. Es decir, calloc() asigna memoria suficiente para un arreglo que contiene numElem elementos con un tamaño tamElem cada uno. Por ejemplo: 21 int *ptr; ptr = (int *)calloc( 10, sizeof(int) ); //otra forma de generar la memoria //dinamica seria: c = (char *)calloc( 1, sizeof(char) ); entero = (int *)calloc( 1, sizeof(int) ); flotante = (float *)calloc( 1, sizeof(float) ); doble = (double *)calloc( 1, sizeof(double) ); #include <stdio.h> #include <stdlib.h> int main( void ){ char *c; int *entero; float *flotante; double *doble; c = (char *)calloc( 1,1 ); entero = (int *)calloc( 1,4 ); flotante = (float *)calloc( 1,4 ); doble = (double *)calloc( 1,8 ); *c = 'c'; *entero = 2378; *flotante = 128.89172378; *doble = 18947282.48263; printf( "valores: caracter %c, entero %d, flotante %f, doble %lf", *c, *entero, *flotante, *doble ); free( c ); free( entero ); free( flotante ); free( doble ); return 0; } 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • Ejemplo: *c = 'a'; *entero = 10; *flotante = 3.89172378; *doble = 1.48263; printf( "valores: caracter %c, entero %d, flotante %f, doble %lf \n\n", *c, *entero, *flotante, *doble ); //Importantisimo liberar la memoria //cuando ya no es requerida free( c ); free( entero ); free( flotante ); free( doble ); 22 • realloc() es la tercera función para obtener memoria dinámica. También devuelve un apuntador void* que hace referencia al bloque de memoria por lo tanto necesario realizar un cast a un apuntador valido. • A diferencia de malloc() y calloc(), realloc() cambia el tamaño de un bloque de memoria asignado dinámicamente, es decir, toma como parámetro de entrada un apuntador *ptr a esa memoria y dependiendo de un segundo parámetro incrementará o reducirá el tamaño de dicho bloque de memoria. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez Función realloc() 23 void* realloc(void* ptr, size_t t); • Donde: • ptr: es el apuntador que hace referencia a un bloque de memoria generado dinámicamente. • t: es el nuevo tamaño en bytes para el bloque de memoria referenciado por ptr. t pude ser mayor o menor que el bloque original. • Se devuelve un apuntador debido a que puede que realloc() tenga que desplazar el bloque original para poder modificar su tamaño. Si este es el caso, se copia la información del bloque original (hasta t bytes) en el nuevo bloque. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • El prototipo de la función es: 24 puntero = (tipo*) realloc( ptr, nuevoTam ); Donde: puntero: es un apuntador valido que hace referencia al bloque de memoria generado. tipo *: es el cast al un apuntador valido. ptr: el apuntador que hace referencia a un bloque de memoria generado dinámicamente. nuevoTam: es el nuevo tamaño para el bloque de memoria referenciado por ptr. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • Forma de uso: 25 1. En el C99 se especifico que la memoria referenciada por ptr se libera y se crea un nuevo bloque. 2. El nuevo bloque contiene la misma información que el bloque original (hasta la longitud especificada por nuevoTam). Se regresa un apuntador al nuevo bloque de memoria, sin embargo, el compilador puede generar el nuevo bloque a partir de la misma dirección de inicio del bloque anterior, es decir, el nuevo bloque puede contener la misma dirección de inicio que el bloque anterior. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez • Hay que tener en cuanta varias consideraciones al utilizar la función realloc(): 26 4. Si no hay memoria suficiente para asignar nuevoTam bytes, realloc() devuelve un apuntador NULL y el bloque original permanecerá intacto. 18 Memoria dinámica y su uso en C Algoritmia y programación estructurada Prof. Edgardo Adrián Franco Martínez 3. Si ptr es NULL, realloc() simplemente genera el bloque de memoria especificado por nuevoTam. Si nuevoTam es cero, se libera la memoria referenciada por ptr y la función devuelve NULL. 27