Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Regla 0x301Eh: Asigná NULL al puntero tras liberar un recurso opaco en el ámbito del cliente

Memoria, punteros y tipos (0x30XX)

Universidad Nacional de Río Negro

0x301Eh: Asigná NULL al puntero tras liberar un recurso opaco en el ámbito del cliente

Enunciado normativo

Tras invocar la función destructora de un recurso opaco, DEBE asignarse NULL al puntero del cliente que lo referenciaba. Si la interfaz ofrece una variante que recibe T ** y anula internamente, DEBE preferirse esa variante.

¿Por qué existe esta regla?

El problema

La función destructora de un TAD opaco libera el recurso, pero recibe el puntero por valor: no puede anular la variable del cliente, que sigue conteniendo la dirección del bloque ya liberado. Ese es el dangling pointer. En un tipo opaco el cliente no puede inspeccionar campos: la única señal de validez es el propio puntero, y por eso hay que anularlo de forma inmediata y visible.

Consecuencias de violarla

Tipo de consecuenciaEfecto concreto
Comportamiento indefinidoVolver a usar el puntero desreferencia memoria liberada (C11 §6.5.3.2).
Doble liberaciónUn segundo lista_destruir(lista) corrompe el heap y aborta con SIGABRT.
Bug silenciosoSi el bloque fue reutilizado, el programa lee datos ajenos sin fallar de inmediato.

Fundamento en el estándar y en la cátedra

C11 §7.22.3.3 declara que el valor de un puntero es indeterminado después de free(); leerlo para compararlo contra NULL no es válido. Anularlo restaura un valor determinista. Liberar y no anular equivale a dejar una trampa abierta.

Alcance y excepciones

Aplica al código cliente que destruye una instancia opaca, y también al ámbito interno de un módulo que destruye recursos locales. No aplica a punteros que quedan fuera de alcance inmediatamente después, ni a variables const que no van a reutilizarse. Tampoco reemplaza la regla general de 0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes para memoria dinámica común: acá el foco está en la asimetría del destructor que no puede anular por sí mismo.

Ejemplos exhaustivos

❌ Contraejemplo 1 — Uso posterior al destructor

lista_t *lista = lista_crear();
if (lista == NULL)
{
    return -1;
}
lista_agregar(lista, 42);
lista_destruir(lista);
printf("Cantidad: %zu\n", lista_cantidad(lista));

Por qué falla: la impresión vuelve a pasar la dirección liberada. Si el asignador reutilizó el bloque, lista_cantidad lee un struct que ya no es una lista; si no, accede a memoria muerta. El error no ocurre en la línea del destructor, sino una línea después.

❌ Contraejemplo 2 — Destrucción repetida dentro de un lazo

for (size_t i = 0; i < n; i++)
{
    procesar(recursos[i]);
    lista_destruir(recursos[i]);
}

Por qué falla: si recursos[i] reaparece apuntando al mismo bloque (por una copia previa), la segunda invocación deriva en double free. Anular cada elemento inmediatamente convierte un error catastrófico en un no-op silencioso, porque free(NULL) está permitido.

✅ Ejemplo conforme 1 — Anulación explícita en el cliente

lista_t *lista = lista_crear();
if (lista == NULL)
{
    return -1;
}
lista_destruir(lista);
lista = NULL;

Después de la segunda línea, cualquier uso accidental del puntero se detecta como NULL y no como memoria liberada. La carga de la prueba pasa a la lógica del programa, no al asignador.

✅ Ejemplo conforme 2 — Destructor que recibe doble puntero

/* lista.h */
void lista_destruir(lista_t **lista);
/* lista.c */
void lista_destruir(lista_t **lista)
{
    if (lista == NULL || *lista == NULL)
    {
        return;
    }
    liberar_nodos((*lista)->cabeza);
    free(*lista);
    *lista = NULL;
}

La interfaz resuelve la asimetría de raíz: recibe la dirección de la variable del cliente y la anula ella misma. El llamador queda libre de recordar la asignación y el patrón es imposible de olvidar.

⚠️ Casos límite

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cLlamada al destructor sin asignación = NULL posterior.
gcc / clanggcc -Wall -Wextra -std=c11 ...No la detecta por sí solo; -Wuse-after-free ayuda en casos simples.
valgrindvalgrind ./programaInvalid read posterior a free, o double free.

Checklist de autocontrol

Reglas relacionadas