You are on page 1of 7

Manejo de memoria en C

Todas las variables, en el lenguaje C, se denen dentro de alguna funcin, fuera de esa funcin no es posible acceder a ellas. Al entrar a una funcin, a cada una de las variables denidas en esa funcin se le asigna el espacio que sea necesario dentro de una pila interna de memoria (stack) con la que cuenta el programa, y al terminar la funcin se desapila todo lo denido en ella. Es decir que la pila crece con cada llamado a una funcin y decrece con cada funcin que se termina. Por otro lado, todos los tipos que C dene como parte del lenguaje son de un tamao jo, incluso los denidos por el usuario usando struct. Es por eso que el espacio que se reserva en la pila interna de memoria tiene un tamao jo. Existe, adems, otro espacio de memoria que se utiliza cuando el tamao de los datos no es jo. A este espacio de memoria dinmica se lo llama heap, contiene bloques de memoria, que el programador puede solicitar para utilizar segn sea conveniente.

1.

Obtener el tamao de un tipo (sizeof())

El operador sizeof() devuelve el tamao en bytes de un tipo de datos (como int). Por comodidad se le puede pasar tanto el nombre de una variable o el nombre de un tipo de datos, en ambos casos devolver el tamao del tipo de datos asociado. int largo, a; largo = sizeof(int); largo = sizeof(a); // Cantidad en bytes de int // Identico a lo anterior

En este caso, ambas llamadas a sizeof devuelven el tamao que ocupa un entero en memoria en la arquitectura y compilador que se est utilizando (por lo general son 4 bytes). char c; largo = sizeof(c); // Cantidad de bytes de un char En este caso, se devuelve cunto ocupa un caracter. Los caracteres ocupan siempre 1 byte. char *puntero; largo = sizeof(puntero); // Cantidad en bytes de un puntero. En este caso, se devuelve la cantidad de bytes que ocupa un puntero. Todos los punteros tienen el mismo tamao, y es tal que pueda contener cualquier direccin de memoria, sea esttica o dinmica. int vector[100]; largo = sizeof(vector); largo = sizeof(vector) / sizeof(int); // Cantidad de bytes del vector // Largo del vector (100) 1

2. Memoria dinmica en C

En este caso, la primera llamada devuelve el tamao total en bytes ocupado por el vector (usualmente seran 400 bytes), mientras que la segunda devuelve siempre 100 sin importar la plataforma, ya que divide el espacio total del vector por el tamao de cada uno de los elementos.

2.

Memoria dinmica en C

Mediante la utilizacin de los punteros, es posible acceder a cualquier porcin de memoria vlida, tanto si se encuentra dentro de la pila interna como si se encuentra dentro del espacio de memoria dinmica. Para obtener una porcin de memoria vlida dentro del espacio de memoria dinmica, existen en la biblioteca estndar funciones (malloc y realloc) que reservan un bloque de memoria y devuelven su direccin. Utilizando estas funciones es posible conseguir estructuras de datos dinmicas, que pueden variar su tamao segn sea necesario, en lugar de tener un tamao ya denido. Es importante notar que la memoria dinmica reservada mediante las funciones de la biblioteca estndar no es liberada automticamente, quien haya hecho la reserva de memoria debe encargarse tambin de liberarla (con la correspondiente funcin de biblioteca estndar, free), de no ser as, se dice que el programa pierde memoria, ya que los bloques reservados no pueden volverse a utilizar an cuando ya no estn en uso.

2.1.

Pedir memoria al sistema (malloc())

Para pedir memoria al sistema se utiliza la funcin malloc1 , denida en <stdlib.h>, cuyo prototipo es el siguiente: void *malloc(size_t tamanio); Si el sistema tiene suciente memoria disponible, malloc devuelve un puntero a la primera posicin de memoria de un bloque de memoria dinmica, de tamanio bytes. Si ocurriera algn problema, porque el sistema no tuviera suciente memoria disponible o similar, la llamada de malloc devolvera NULL.

2.2.

Conversin forzada de tipos (cast)

La conversin forzada, o casteo, se utiliza para convertir un valor de un tipo a otro, cuando el compilador no es capaz de hacerlo automticamente. Se lo logra anteponiendo un tipo entre parntesis delante de una expresin. Por ejemplo: int *datos = (int*) malloc( 128*sizeof(int) ); En este caso se reserva un bloque de memoria para 128 enteros, y luego se lo asigna a un puntero de tipo int *, pero para poder hacerlo es necesario convertir el puntero genrico void * devuelto por malloc a un puntero a enteros.

3.

Devolver memoria al sistema (free())

Cuando un bloque de memoria ya no es necesario para un programa, se lo debe devolver al sistema, de forma que el sistema pueda tenerlo nuevamente entre los recursos a utilizar por
1

Para ms informacin: man 3 malloc.

4. Agrandar/achicar un bloque de memoria (realloc())

otros procesos. La funcin free2 de la biblioteca estndar hace exactamente esto, su prototipo es: void free(void *puntero); Recibe como nico parmetro un puntero antes devuelto por malloc o realloc, libera ese bloque de memoria y no devuelve nada. Es importante notar que no es posible liberar una porcin de memoria que ya ha sido liberada. Esta accin puede provocar el mismo tipo de errores que los provocados por acceder a una porcin de memoria invlida.

4.

Agrandar/achicar un bloque de memoria (realloc())

Cuando se necesita modicar el tamao de un bloque memoria, se utiliza la funcin realloc3 de la biblioteca estndar. Su prototipo es el siguiente: void *realloc(void *puntero_anterior, size_t nuevo_tamanio); Recibe un puntero antes obtenido mediante malloc o realloc y el nuevo tamao del bloque de memoria. Si todo funciona bien, devuelve un nuevo puntero al nuevo bloque de memoria, copia el contenido del bloque viejo al nuevo (copia el largo mnimo entre los dos bloques), y el bloque anterior es liberado. Si algo falla, devuelve NULL, y el bloque anterior no se modica. Teniendo en cuenta este comportamiento, normalmente no se utiliza una construccin como la siguiente. /* MAL */ datos = (dato_t*) realloc( datos, sizeof(dato_t) * tamanio_nuevo); Ya que si realloc devolviera NULL, existir en memoria un bloque de datos vlido, con informacin vlida, al cual es imposible acceder, ya que se perdi la direccin de memoria que antes estaba en datos. En cambio se debe utilizar: void *aux = realloc( datos, sizeof(dato_t) * tamanio_nuevo ); if ( aux == NULL ) { // realloc no pudo pedir el bloque nuevo, hacer algo al respecto. } else { datos = (dato_t*) aux; } Otro posible problema a tener en cuenta es cuando se tienen punteros que apuntan a partes de un bloque de memoria y se llama a realloc sobre ese bloque: los punteros pasarn a ser invlidos, ya que apuntan a direcciones que ya no son las del bloque en cuestin. De modo que hay que tener mucho cuidado de nunca guardar referencias a porciones de memoria que pueden ser movidas de lugar mediante realloc.
2 3

Para ms informacin: man 3 free. Para ms informacin: man 3 realloc.

5. Ejemplo: una pila de tamao variable

5.

Ejemplo: una pila de tamao variable

Tener una pila de tamao variable es un ejemplo sencillo de manejo de memoria dinmica. La pila contiene un arreglo de valores, donde se van apilando y desapilando los elementos; el problema se da cuando se quieren apilar ms elementos que los que la pila puede almacenar, en ese caso se debe reservar un bloque de memoria de mayor tamao para que siga siendo posible agregar elementos a la pila. En este caso, la estructura de la pila ser de la forma: typedef struct { int cantidad; dato_t *datos; int tamanio; } pila_dinamica; Donde cantidad es la cantidad de elementos almacenada en la pila, mientras que tamanio es el tamao actual de la pila, es decir, la mxima cantidad de elementos que puede almacenar antes de tener que reservar una porcin mayor de memoria.

5.1.

Creacin de la pila

Cuando se trabaja con memoria dinmica, las funciones de creacin de una estructura, no slo deben inicializar los atributos de la estructura, sino que tambin deben hacer el primer pedido de memoria, para reservar el bloque inicial con el que se trabajar. bool pila_crear(pila_dinamica *pila) { pila->cantidad = 0; pila->datos = (dato_t *) malloc( TAM_INICIAL * sizeof(dato_t) ); if (pila->datos == NULL) return false; pila->tamanio = TAM_INICIAL; return true; } La funcin malloc es la encargada de reservar un bloque de memoria para los datos que contendr la pila. El tamao de este bloque de memoria es el argumento que se le da a malloc. En este caso es el resultado de multiplicar una constante TAM_INICIAL por el tamao del tipo de dato que contiene la pila, es decir que en primera instancia la pila podr contener TAM_INICIAL elementos. Si por algn motivo el sistema operativo no pudiera reservar la memoria requerida, la funcin malloc devuelve NULL. En ese caso, la funcin de creacin de la pila devuelve false para indicar que no se ha podido crear la pila.

5.2.

Incremento del tamao de la pila

En el caso de agotarse el lugar, ser necesario reservar una porcin mayor de memoria. Es decir que la funcin apilar deber ser de la forma: bool pila_apilar(pila_dinamica *pila, dato_t valor) { if (pila->cantidad >= pila->tamanio) {

5. Ejemplo: una pila de tamao variable

if (! pila_cambiar_tamanio(pila, pila->tamanio*2)) return false; } pila->datos[pila->cantidad++] = valor; return true; } En esta funcin, cuando la cantidad de elementos es igual o mayor al tamao actual de la pila, se llama a la funcin pila_cambiar_tamanio, que ser la encargada de reservar un bloque de mayor tamao, en este caso se le pide que el bloque sea del doble del tamao original. La funcin pila_cambiar_tamanio tendr la siguiente forma: bool pila_cambiar_tamanio(pila_dinamica *pila, size_t tamanio) { void *aux = realloc (pila->datos, sizeof(dato_t) * tamanio); if (aux == NULL) return false; pila->datos = (dato_t*) aux; pila->tamanio = tamanio; return true; } En este caso se utiliza la funcin realloc, que recibe la direccin actual donde se encuentran los datos, y el nuevo tamao que se quiere reservar. realloc se encarga de reservar el nuevo bloque, copiar toda la informacin que estaba en el bloque viejo al nuevo y liberar el viejo. Al igual que en el caso de la creacin de la pila, si por algn motivo no es posible reservar la memoria segn se quiere, realloc devuelve NULL. En este caso, los valores que ya estaban en la pila siguen estando ah, simplemente signica que no se ha podido agrandar la porcin de memoria reservada segn se haba pedido, es por ello que se utiliza un puntero auxiliar aux y slo se lo asigna al atributo datos en el caso en que la reserva de memoria haya sido exitosa.

5.3.

Destruccin de la pila

Como ya se dijo, cuando se reserva memoria mediante estas funciones, es importante luego liberar la memoria reservada, porque de no hacerlo quedan bloques de memoria inutilizables. Es por ello que ser necesario contar con una funcin pila_destruir, y quien utilice la pila deber recordar llamar a esta funcin al terminar de utilizarla. bool pila_destruir(pila_dinamica *pila) { free(pila->datos); } Una vez que se ha llamado a la funcin free, la porcin de memoria dinmica deja de estar reservada, ya no es ms una porcin de memoria vlida y no puede ser accedida por el programa (a menos que se haga una nueva reserva).

5.4.

Disminucin del tamao de la pila

Finalmente, si bien es posible tener una pila que slo crezca y nunca se reduzca, en general es deseable liberar la memoria que no est siendo utilizada, para que pueda ser usada por otras partes del programa. De modo que sera deseable que la pila se reduzca al desapilar, cuando el espacio ocupado por los elementos en mucho menor que el tamao de la pila.

6. Aritmtica de punteros bool pila_desapilar(pila_dinamica *pila, dato_t *valor) { if ( ! pila_ver_tope(pila, valor) ) return false; pila->cantidad--; if ( pila->cantidad < (pila->tamanio / 4) ) { pila_cambiar_tamanio(pila, pila->tamanio/2); } return true; }

En este caso, luego de desapilar el elemento pedido, la funcin verica si la cantidad de elementos ocupa menos de un cuarto del tamao total de la pila, y de ser as, reduce el tamao a la mitad.

6.

Aritmtica de punteros

Las direcciones de memoria en C son valores enteros positivos, el valor NULL es equivalente a 0, que es una posicin de memoria invlida. Los punteros contienen direcciones de memoria asociadas a un tipo en particular (a excepcin de el tipo void). Como ya se vio, cada tipo tiene asociado un tamao en bytes. El compilador de C utiliza esta informacin para poder realizar operaciones aritmticas (sumas y restas de valores enteros) con punteros. Este es un tema que se presenta en principio complejo, pero que hace que se pueda operar de forma muy poderosa sobre las porciones de memoria utilizadas. En particular, es posible utilizar esta tcnica para recorrer un arreglo de valores, sin que necesariamente se los haya declarado como vector. Ejemplo: float fs[MAX_LARGO]; float *pf = fs; pf = pf + 1; pf = fs + MAX_LARGO - 1; // pf apunta al comienzo del vector fs // pf apunta al segundo elemento de fs (&fs[1]) // pf apunta a el ltimo elemento de fs

En este caso se declara un vector de valores de tipo float (que ocupan 4 bytes cada uno), luego se declara un puntero a valores de tipo float, que se inicializa con la posicin de memoria del primer elemento del vector. Al sumarle 1, sin embargo, la posicin de memoria se incrementa 4 bytes, ya que se trata de un puntero y de esta manera avanza al siguiente float del vector. De la misma manera, en la ltima lnea, se obtiene un puntero a la direccin de memoria del ltimo elemento del arreglo. Si no se declara el puntero del tipo correcto, en cambio, no es posible operar de esta manera con las direcciones de memoria. Es decir que si se hiciera algo como lo siguiente: void *pv = fs; pv = pv+1; // pv apunta al comienzo del vector fs (sin tipo asociado) // pv apunta al segundo byte de fs *ERROR*

Se obtendra un puntero a la direccin de memoria del segundo byte del vector de float, lo cual podra dar lugar a diversos errores, ya que si se quiere acceder a la informacin, se estara accediendo a 3 bytes de un valor y 1 byte del siguiente.

7. Uso directo de bloques de memoria

Es por ello que es posible decir que el operador de acceso a un elemento [] en C es azcar sintctico4 , siendo a[10] sintcticamente equivalente a *(a+10), as como a 10[a]. Claro que este ltimo, si bien vlido, hace que el cdigo sea extremadamente poco legible, por lo que no se lo debe utilizar.

7.

Uso directo de bloques de memoria

En la biblioteca estndar de C hay varias funciones tiles que acceden directamente a la memoria, que permiten copiar o inicializar valores, que, por lo general, sern necesarias cuando se trabaje con bloques de memoria. Estas funciones asumen que tanto el bloque de memoria origen y destino son porciones vlidas de memoria, y que pueden ser escritas desde el programa. De no ser as, el sistema probablemente termine la ejecucin del programa al encontrar un acceso a una porcin de memoria invlida, generalmente mediante el error Violacin de segmento (Segmentation fault). Adems, es importante tener en cuenta que todas estas operaciones tienen un costo lineal con respecto al tamao de memoria sobre el cual operan, es decir que el tiempo requerido para ejecutarlas depende del tamao de los bloques de memoria.

7.1.

Copiar contenidos de bloques de memoria (memcpy() y memmove())

Para copiar el contenido de un bloque memoria a otro se puede utilizar la funcin memcpy5 , declarada en el encabezado <string.h>, cuyo prototipo es: void *memcpy(void *destino, const void *origen, size_t cantidad); La funcin copia cantidad bytes desde la posicin de memoria origen hacia la posicin destino y devuelve un puntero a destino. La funcin asume que origen y destino son bloques que no se solapan. Cuando origen y destino se solapan se debe utilizar la funcin memmove6 , tambin declarada en el encabezado <string.h>, cuyo prototipo es: void *memmove(void *destino, const void *origen, size_t cantidad); La funcin copia cantidad bytes desde la posicin de memoria origen hacia la posicin destino y devuelve un puntero a destino. De haber solapamiento, se encarga de que no se pierdan los datos al momento de hacer la copia.

7.2.

Inicializacin de un bloque de memoria (memset())

Para inicializar un bloque de memoria con un valor se puede utilizar la funcin memset7 , denida en el encabezado <string.h>, cuyo prototipo es: void *memset(void *direccion, int byte, size_t cantidad); Que escribe el valor de byte, en el bloque de cantidad bytes, que empieza en direccion. Devuelve un puntero a direccion.

4 5

Un agregado a la sintaxis del lenguaje para hacerla ms agradable, pero no imprescindible. Para ms informacin: man 3 memcpy. 6 Para ms informacin: man 3 memmove. 7 Para ms informacin: man 3 memset.

You might also like