Regla 0x300Fh: Liberá la memoria en el orden inverso a su asignación
Memoria, punteros y tipos (0x30XX)
0x300Fh: Liberá la memoria en el orden inverso a su asignación¶
Enunciado normativo¶
La memoria de una estructura anidada DEBE liberarse en el orden inverso al de su asignación: primero los recursos internos (los más profundos) y al final el contenedor que los referencia.
¿Por qué existe esta regla?¶
El problema¶
Cuando una estructura contiene punteros a otros bloques —una matriz, una lista, un struct con texto— la liberación va de adentro hacia afuera. Si se libera primero el contenedor, se pierde la única referencia a los bloques internos y se produce una fuga de memoria. El orden inverso refleja las dependencias: el contenedor depende de los elementos y debe vivir al menos tanto como ellos.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Fuga de memoria | Los bloques internos quedan inalcanzables; valgrind los reporta como lost. |
| Uso de memoria liberada | Un puntero interno se usa después de liberar el contenedor. |
| Escalabilidad | En un lazo, las fugas acumuladas agotan la memoria del proceso. |
Fundamento en el estándar y en la cátedra¶
C11 §7.22.3.3 exige que cada bloque asignado se libere exactamente una vez; con estructuras anidadas, la única forma es recorrer la jerarquía en orden inverso. La cátedra lo adopta junto con 0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes (liberar y anular).
Alcance y excepciones¶
Aplica a toda estructura con más de un bloque: matrices T **, listas, árboles,
structs con punteros a cadenas. No aplica cuando todos los datos viven en un
único bloque contiguo: se libera una sola vez y no hay orden que respetar.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Liberar el contenedor antes que los nodos¶
void lista_destruir(lista_t *lista)
{
free(lista);
while (lista->cabeza != NULL)
{
nodo_t *siguiente = lista->cabeza->sig;
free(lista->cabeza);
lista->cabeza = siguiente;
}
}Por qué falla: free(lista) se ejecuta primero y el bucle accede a
lista->cabeza después de liberar lista (use-after-free). El contenedor
debía sobrevivir hasta terminado el recorrido.
❌ Contraejemplo 2 — Matriz liberada al revés¶
double **matriz = reservar_matriz(filas, columnas);
free(matriz);
for (size_t i = 0; i < filas; i++)
{
free(matriz[i]);
}Por qué falla: se libera el arreglo de punteros antes que las filas. Las filas
quedan inalcanzables y el bucle ya no puede acceder a matriz[i]. Cada fila es
un bloque perdido. El orden correcto es primero las filas y después el arreglo
de punteros.
✅ Ejemplo conforme 1 — Contenedor al final¶
void lista_destruir(lista_t *lista)
{
nodo_t *actual = lista->cabeza;
while (actual != NULL)
{
nodo_t *siguiente = actual->sig;
free(actual);
actual = siguiente;
}
free(lista);
}Los nodos se liberan primero, conservando el contenedor para recorrerlos; al final se libera el contenedor. Cada bloque conoce a su siguiente antes de liberarse, de modo que no se pierde ninguna dirección.
✅ Ejemplo conforme 2 — Matriz en orden inverso completo¶
void liberar_matriz(double **matriz, size_t filas)
{
if (matriz == NULL)
{
return;
}
for (size_t i = 0; i < filas; i++)
{
free(matriz[i]);
}
free(matriz);
}Primero las filas (los recursos internos) y al final el arreglo de punteros. Si
la reserva asignó fila por fila, este recorrido reproduce el orden inverso de la
asignación. La función tolera NULL y no deja bloques colgados.
⚠️ Casos límite¶
Puntero base desplazado: si se perdió el original por avanzar el puntero, no hay forma de liberar correctamente; conservá el base.
Recursos con distinto dueño: una fila puede pertenecer a otro módulo y no debe liberarse acá; documentalo conforme a 0x3006h: Documentá la propiedad de los recursos al utilizar punteros.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Liberación del contenedor antes de sus miembros. |
gcc / clang | gcc -Wall -Wextra -std=c11 -fanalyzer ... | use after 'free' en caminos anidados. |
valgrind | valgrind --leak-check=full ./programa | indirectly lost / definitely lost de bloque interno. |
Checklist de autocontrol¶
¿Libero primero los recursos internos?
¿El contenedor se libera al final?
¿Conservo el puntero base para poder liberar?
¿Respetué la propiedad de recursos ajenos al módulo?
Reglas relacionadas¶
0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes — cada liberación se acompaña de la anulación del puntero.
0x3014h: Prohibición de doble liberación de memoria (double free) sobre el mismo puntero — el orden descuidado suele derivar en doble liberación.
0x3006h: Documentá la propiedad de los recursos al utilizar punteros — saber quién es dueño es requisito para liberar en orden.
0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr) — el tamaño correcto de cada bloque sostiene la liberación segura.