Regla 0x3015h: Reallocación segura: no sobreescribir el puntero original directamente
Memoria, punteros y tipos (0x30XX)
0x3015h: Reallocación segura: no sobreescribir el puntero original directamente¶
Enunciado normativo¶
El resultado de
reallocDEBE recibirse en una variable temporal y DEBE validarse contraNULLantes de reemplazar el puntero original. NO DEBE escribirseptr = 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 consecuencia | Efecto concreto |
|---|---|
| Fuga de memoria | El bloque original queda inalcanzable ante el fallo. |
| Bug silencioso | Sólo se manifiesta cuando el sistema se queda sin memoria. |
| Comportamiento indefinido | Si se usa ptr tras el fallo, es un puntero nulo. |
| Robustez | La 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¶
realloc(ptr, 0): el comportamiento es definido por la implementación; el estándar permite devolverNULLo un puntero quefreeacepta. Evitalo; usáfreeexplícito si querés liberar.realloc(NULL, n): equivale amalloc(n); la variable temporal sigue siendo buena práctica.Reintentos: si vas a reintentar, conservá el temporal fuera del alcance del intento y no pierdas el original.
Cero elementos: verificá que la cantidad sea mayor que cero antes de reservar;
malloc(0)tiene el comportamiento ya discutido.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | ptr = realloc(ptr, ...) sobre el mismo puntero. |
gcc / clang | gcc -Wall -Wextra -std=c11 -fanalyzer ... | realloc con posible fuga del original en el camino de fallo. |
valgrind | valgrind --leak-check=full ./programa | definitely lost del bloque original ante fallo forzado. |
Checklist de autocontrol¶
¿Recibo
reallocen una variable temporal?¿Verifico el temporal contra
NULLantes de asignar?¿El bloque original queda accesible si la reasignación falla?
¿Usé
sizeof(*temporal)y nosizeof(temporal)?¿Evité
ptr = realloc(ptr, n)?
Reglas relacionadas¶
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — toda reserva, incluida
realloc, se verifica contraNULL.0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr) — el tamaño se calcula con
sizeof(*ptr).0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes — el bloque original sigue el ciclo liberar y anular.
0x3006h: Documentá la propiedad de los recursos al utilizar punteros — documentar si la función conserva o transfiere la propiedad.
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¶
Si falla: se pierde el puntero original (memory leak) y
ptrqueda enNULL, con riesgo inmediato deSIGSEGVsi se usa.Si tiene éxito: funciona, por lo que el bug no se manifiesta en el caso feliz y solo aparece bajo presión de memoria.
Detectar la fuga requiere un analizador (Valgrind, LeakSanitizer); el compilador no advierte.
Fundamento en el estándar C11¶
C11 §7.22.3.5p3: si
reallocno puede asignar, devuelve un puntero nulo y el bloque original permanece sin cambios (no se libera).C11 §7.22.3.5p2: si la reasignación ocurre en un bloque nuevo, el anterior se libera y los bytes hasta el mínimo de ambos tamaños se copian.
La regla 0x3015h cristaliza la única secuencia segura: guardar en temporal, verificar y recién entonces reasignar.
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¶
realloc(ptr, 0)es equivalente afree(ptr); el retorno puede serNULLy no debe tratarse como error de asignación.El tipo del temporal debe ser el mismo que el de
ptrpara quesizeof(*tmp)mida lo correcto.Si
ptresNULL,realloc(NULL, n)equivale amalloc(n); sigue valiendo la verificación del retorno contraNULL(regla 0x3001h).
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¶
¿
reallocsiempre asigna a una variable temporal?¿Verifiqué
tmp == NULLantes de sobrescribirptr?¿En caso de fallo el bloque original sigue accesible o se liberó?
¿El temporal tiene el mismo tipo que el puntero original?
Reglas relacionadas¶
0x3015h: Reallocación segura: no sobreescribir el puntero original directamente — reallocación segura con temporal.
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — verificar el retorno de la reasignación.
0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes — liberar y anular punteros.
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — el
NULLdereallocsin chequear.