Estoy trabajando en un proyecto C (tarea para la escuela). Una de las demandas es que en caso de fallo de malloc() , el programa debe free() toda la memoria asignada y exit() .
Considere un caso donde la función A() construye una lista enlazada y en cada iteración llama a otra función, B() . Ahora, si ocurre una falla de malloc en B() , debe free() la memoria que asignó, pero la función A() también debería hacerlo.
Las cosas se complican bastante cuando tienes un árbol de llamadas a funciones mayor que dos.
En mi proyecto anterior, usé una bandera para notificar una falla de malloc(): si una función usa otra función que puede usar malloc() , debe verificar la bandera inmediatamente después. Funcionó, pero el código se volvió un poco desordenado.
¿Hay una solución ordenada para este problema?
Por supuesto, con las aplicaciones "reales" toda la memoria es desasignada por el sistema operativo, pero supongo que esta demanda es pedagógica.
Creo que el enfoque más fácil es crear un asignador personalizado (como alguien ya señaló en una publicación eliminada) para realizar un seguimiento de todas sus asignaciones, luego hacer un asignador personalizado, utilícelo para todas sus necesidades de memoria en montón.
si un malloc falla, tiene la lista de bloques previamente asignados al alcance de la mano.
por ejemplo (debe rehacer esto porque no es efectivo y debe optimizarse pero muestra el principio y la única compilación ocular)
typedef struct { void* pMemory; /* for the allocated memory */ size_t size; /* for better debugging */ } MemoryBlock; #define MAXBLOCKS 1000 MemoryBlock myheap[MAXBLOCKS]; // global so zero:ed static int block = 0; void* myalloc(size_t size) { static int block = 0; // you should check vs MAXBLOCKS myheap[block].pMemory = malloc(size); myheap[block].size = size; // check if it failed. if ( myheap[block].pMemory == NULL ) { for (int i = 0; i < block; ++i) { myfree(myheap[i].pMemory); } fprintf( stderr, "out of memory\n"); exit(EXIT_FAILURE); } else { return myheap[block++].pMemory; } } void myfree(void* p) { for (int i = 0; i < block; ++i) { if ( p == myheap[i].pMemory ) { free(myheap[i].pMemory); myheap[i].pMemory = NULL; return; } } }Sí. La mejor (y convencional) forma es inicializar cada valor de puntero a cero. Luego configúrelo durante la asignación de malloc(). Ej: myPtr = malloc( 10 );
Será cero en caso de falla, y lo verifica. Y finalmente, cuando vas a liberar, siempre verificas el valor del puntero antes de llamar a free():
if ( myPtr != 0 ) free( myPtr );No hay necesidad de una bandera adicional.
Puede buscar en la función atexit() para registrar el código que se ejecutará cuando finalice el programa. Dicho código puede verificar si hay algo que deba estar free() d.
Tenga en cuenta que atexit() no tiene forma de cancelar el registro. Por lo tanto, debe asegurarse de registrar cada función de limpieza solo una vez y de que haga lo correcto cuando no haya nada que limpiar.
#include <stdlib.h> #include <stdio.h> int *ptr1; char *ptr2; int clean1_registered, clean2_registered; void clean1(void) { printf("clean1 called\n"); if (ptr1) { free(ptr1); ptr1 = NULL; } } void clean2(void) { printf("clean2 called\n"); if (ptr2) { free(ptr2); ptr2 = NULL; } } void B(void) { ptr2 = malloc(100); if (!clean2_registered) { atexit(clean2); } } void A(void) { ptr1 = malloc(100 * sizeof(int)); if (!clean1_registered) { atexit(clean1); } B(); } int main(int argc, char **argv) { A(); }