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 0x3015h: Reallocación segura: no sobreescribir el puntero original directamente

Memoria, punteros y tipos (0x30XX)

Universidad Nacional de Río Negro

0x3015h: Reallocación segura: no sobreescribir el puntero original directamente

Enunciado normativo

El resultado de realloc DEBE recibirse en una variable temporal y DEBE validarse contra NULL antes de reemplazar el puntero original. NO DEBE escribirse ptr = realloc(ptr, n).

¿Por qué existe esta regla?

El problema

realloc(ptr, n) puede fallar y devolver NULL sin liberar el bloque original, que sigue siendo válido y reservado. Si el programador escribe ptr = realloc(ptr, n) y realloc falla, ptr pasa a valer NULL: la dirección del bloque viejo se pierde para siempre y su memoria queda inalcanzable. Es una fuga de memoria que ocurre justo cuando el sistema está bajo presión de memoria, el peor momento posible.

La solución es recibir el resultado en una variable temporal: si es NULL, el original sigue disponible para liberarlo o para seguir usándolo; si no, se confirma la asignación al puntero definitivo.

Consecuencias de violarla

Tipo de consecuenciaEfecto concreto
Fuga de memoriaEl bloque original queda inalcanzable ante el fallo.
Bug silenciosoSólo se manifiesta cuando el sistema se queda sin memoria.
Comportamiento indefinidoSi se usa ptr tras el fallo, es un puntero nulo.
RobustezLa aplicación se degrada justo cuando más necesita reservar.

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

C11 §7.22.3.5 especifica que, si realloc no puede satisfacer la solicitud, devuelve NULL y no libera el bloque original. La cátedra adopta la variable temporal como patrón obligatorio, en el marco de 0x3001h: Siempre verificá la asignación exitosa de memoria dinámica (verificar la asignación) y de 0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes (todo bloque reservado se libera una vez).

Alcance y excepciones

Aplica a todo uso de realloc, incluidos los realloc de tamaño cero, que se discuten como caso límite. No aplica a la primera asignación (que usa malloc o calloc), aunque la verificación es igual. La regla no obliga a abortar ante el fallo: podés conservar el bloque viejo, liberarlo o reintentar, siempre que decidas explícitamente.

Ejemplos exhaustivos

❌ Contraejemplo 1 — Sobreescritura directa

datos = realloc(datos, nueva_cantidad * sizeof(*datos));
if (datos == NULL)
{
    return -1;
}

Por qué falla: si realloc falla, datos se convierte en NULL y el bloque original se filtró antes de que el if pueda verlo. La verificación llega tarde: el daño (la pérdida de la dirección) ya está hecho.

❌ Contraejemplo 2 — Desreferenciar el retorno sin validar

datos = realloc(datos, nueva_cantidad * sizeof(*datos));
datos[nueva_cantidad - 1] = 0;

Por qué falla: si la reasignación falla, se desreferencia NULL y el programa cae con SIGSEGV; además, el bloque viejo se filtró. Faltan la variable temporal y la comprobación de 0x3001h: Siempre verificá la asignación exitosa de memoria dinámica.

✅ Ejemplo conforme 1 — Variable temporal y validación

int *temporal = realloc(datos, nueva_cantidad * sizeof(*temporal));
if (temporal == NULL)
{
    free(datos);
    return -1;
}
datos = temporal;

Si la reasignación falla, datos conserva el bloque original y el programa puede liberarlo o seguir usándolo. sizeof(*temporal) es válido aun con temporal nulo, porque no se evalúa el objeto sino su tipo. La asignación definitiva ocurre sólo tras el éxito.

✅ Ejemplo conforme 2 — Reasignación que conserva el original

recurso_t *crecer(recurso_t *actual, size_t nueva_cantidad)
{
    recurso_t *temporal = realloc(actual, nueva_cantidad * sizeof(*temporal));
    if (temporal == NULL)
    {
        return actual;
    }
    return temporal;
}

La función no sacrifica el recurso ante el fallo: devuelve el puntero original intacto y el llamador decide. Es un contrato robusto y documentable conforme a 0x3006h: Documentá la propiedad de los recursos al utilizar punteros.

⚠️ Casos límite

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cptr = realloc(ptr, ...) sobre el mismo puntero.
gcc / clanggcc -Wall -Wextra -std=c11 -fanalyzer ...realloc con posible fuga del original en el camino de fallo.
valgrindvalgrind --leak-check=full ./programadefinitely lost del bloque original ante fallo forzado.

Checklist de autocontrol

Reglas relacionadas

Antipatrón: Sobreescritura directa de puntero en realloc

Síntoma en el código del estudiante

El retorno de realloc se escribe sobre la misma variable que se le pasa:

ptr = realloc(ptr, nuevo_tam);

El identificador aparece a ambos lados del =, y no existe ninguna variable temporal.

Diagnóstico

Mecanismo del defecto

realloc intenta agrandar (o achicar) el bloque. Si lo logra en el mismo lugar, devuelve la misma dirección. Si no puede y consigue otro bloque, copia el contenido y libera el viejo, devolviendo la dirección nueva.

Cuando la reasignación falla, realloc devuelve NULL y deja el bloque original intacto. Al escribir ptr = realloc(ptr, n), el NULL pisa la única referencia que teníamos al bloque viejo: ni se libera ni se puede volver a encontrar. Es una fuga de memoria irrecuperable, además del NULL listo para ser desreferenciado (ver 0x3001h).

Consecuencia observable

Fundamento en el estándar C11

Corrección idiomática

❌ Código con el antipatrón
int *crecer(int *ptr, size_t n)
{
    ptr = realloc(ptr, n * sizeof(*ptr));
    return ptr;
}

Si realloc devuelve NULL, el bloque viejo quedó inaccesible.

✅ Código refactorizado
int *crecer(int *ptr, size_t n)
{
    int *tmp = realloc(ptr, n * sizeof(*tmp));
    if (tmp == NULL) {
        return ptr;   /* el bloque original sigue siendo válido */
    }
    return tmp;
}
✅ Variante in situ con liberación del viejo
int *tmp = realloc(ptr, n * sizeof(*tmp));
if (tmp == NULL) {
    free(ptr);
    ptr = NULL;
    return NULL;
}
ptr = tmp;

Nótese que primero se comprueba tmp y recién después se toca ptr.

⚠️ Casos límite

Errores típicos al compilar o ejecutar

$ valgrind --leak-check=full ./programa
==1234== LEAK SUMMARY:
==1234==    definitely lost: 40 bytes in 1 blocks
==1234==      at 0x...: malloc (vg_replace_malloc.c)
==1234==      by 0x...: crecer (programa.c:3)

Checklist de verificación

Reglas relacionadas