Regla 0x3001h: Siempre verificá la asignación exitosa de memoria dinámica
Memoria, punteros y tipos (0x30XX)
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica¶
Enunciado normativo¶
Toda llamada a
malloc,callocoreallocDEBE ser seguida inmediatamente por una comprobación del resultado contraNULL, antes de desreferenciar el puntero o de usarlo en cualquier cálculo.
¿Por qué existe esta regla?¶
El problema¶
El asignador puede fallar: el heap se agota, el sistema niega una página o el
tamaño es irrazonable. Entonces malloc, calloc y realloc devuelven NULL,
una dirección que no apunta a ningún objeto válido. Desreferenciarla es
comportamiento indefinido y suele terminar en SIGSEGV; la verificación
convierte ese fallo del entorno en un error controlado.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Comportamiento indefinido | Desreferenciar NULL es UB (C11 §6.5.3.2); suele terminar en SIGSEGV. |
| Bug silencioso | Una cuenta que asume “siempre hay memoria” produce datos corruptos meses después. |
| Robustez | El programa no distingue entre “sin resultados” y “sin memoria”. |
Fundamento en el estándar y en la cátedra¶
C11 §7.22.3 especifica que las funciones de asignación devuelven un puntero nulo si no pueden satisfacer la solicitud. La cátedra trata esa condición como parte del contrato: quien falla debe poder propagarlo, y quien llama, decidir. La regla no exige abortar, exige decidir explícitamente.
Alcance y excepciones¶
Aplica a toda asignación dinámica, incluidas las de un constructor de TAD o una
reasignación intermedia. No aplica a memoria estática ni a variables
automáticas. El caso malloc(0) se discute en casos límite.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Desreferencia inmediata sin comprobar¶
nodo_t *nuevo = malloc(sizeof(*nuevo));
nuevo->dato = valor;
nuevo->sig = NULL;Por qué falla: si el heap no tiene espacio, nuevo es NULL y la asignación a
nuevo->dato escribe en la dirección cero. El chequeo debe ir inmediatamente
después de malloc, no al final de la función.
❌ Contraejemplo 2 — Comprobación después de usar el recurso¶
arreglo = malloc(cantidad * sizeof(*arreglo));
inicializar(arreglo, cantidad);
if (arreglo == NULL)
{
return ERROR_MEMORIA;
}Por qué falla: la verificación llega tarde. inicializar recibe un puntero nulo
y lo desreferencia antes de que el if se evalúe. La comprobación debe ser
inmediata.
✅ Ejemplo conforme 1 — Verificación inmediata y propagación¶
nodo_t *nodo_crear(int valor)
{
nodo_t *nuevo = malloc(sizeof(*nuevo));
if (nuevo == NULL)
{
return NULL;
}
nuevo->dato = valor;
nuevo->sig = NULL;
return nuevo;
}La función documenta su contrato: “devuelve NULL si no hay memoria”. El
llamador decide si aborta, reintenta o degrada la funcionalidad, conforme a
0x3009h: Documentá explícitamente los casos en que una función puede retornar NULL.
✅ Ejemplo conforme 2 — Envoltorio centralizado de asignación¶
static void *reservar(size_t cantidad)
{
void *bloque = malloc(cantidad);
if (bloque == NULL)
{
fprintf(stderr, "Sin memoria para %zu bytes\n", cantidad);
exit(EXIT_FAILURE);
}
return bloque;
}Cuando todo el módulo tiene una política única de fallo (abortar), conviene centralizarla. Así el chequeo aparece una sola vez y ningún camino lo olvida. La verificación sigue existiendo aunque el llamador ya no la repita.
⚠️ Casos límite¶
malloc(0): el estándar permite devolverNULLo un puntero no nulo único. No lo trates como “sin memoria” sin comprobar antescantidad == 0.calloc(n, m): además deNULL, verificá el desbordamiento den * m;calloclo detecta y devuelveNULL, pero tu lógica debe distinguirlo.realloc: si falla devuelveNULLy conserva el bloque original; no reemplaces el puntero hasta validar (ver 0x3015h: Reallocación segura: no sobreescribir el puntero original directamente).
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | malloc/calloc/realloc sin if (... == NULL) asociado. |
gcc / clang | gcc -Wall -Wextra -std=c11 -fanalyzer ... | dereference of possibly-NULL en el flujo de asignación. |
valgrind | valgrind ./programa | Invalid write en dirección 0x0 cuando el fallo se fuerza. |
Checklist de autocontrol¶
¿Cada
malloc,callocyrealloctiene suif (... == NULL)?¿La comprobación está inmediatamente después de la llamada?
¿El fallo se propaga como valor de retorno o se maneja explícitamente?
¿Evité desreferenciar antes de comprobar?
Reglas relacionadas¶
0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr) — el tamaño se calcula con
sizeof, no con constantes mágicas.0x3009h: Documentá explícitamente los casos en que una función puede retornar NULL — la posibilidad de retornar
NULLdebe estar documentada.0x3015h: Reallocación segura: no sobreescribir el puntero original directamente — la reasignación segura es un caso particular de esta regla.
Antipatrón: Uso de memoria dinámica sin validar retorno a NULL¶
Síntoma en el código del estudiante¶
El patrón se reconoce porque la llamada a malloc, calloc o realloc y el
primer uso del puntero aparecen pegados, sin ninguna pregunta intermedia sobre
el valor devuelto:
int *p = malloc(10 * sizeof(*p));
p[0] = 42;No hay if (p == NULL), ni if (!p), ni una cláusula de guarda. El estudiante
asume que “siempre hay memoria” porque en su máquina de escritorio la reserva
casi nunca falla.
Diagnóstico¶
Mecanismo del defecto¶
malloc no garantiza el éxito. Cuando el asignador no encuentra un bloque
libre del tamaño pedido, devuelve el puntero nulo (NULL). Ese valor no es
una dirección válida de objeto: es la constante (void *)0.
El operador de indirección *p (o el subíndice p[0], que es azúcar de
*(p + 0)) exige que p apunte a un objeto. Si p vale NULL, la operación
no tiene un objeto que leer ni escribir: el estándar la declara directamente
comportamiento indefinido (C11 §6.5.3.2p4). En un sistema con memoria
virtual, la dirección 0 suele no estar mapeada, por lo que el hardware
dispara una falla de página irrecuperable y el proceso muere con SIGSEGV; no
obstante, el estándar no promete ese desenlace.
Consecuencia observable¶
En la práctica: caída inmediata con
Segmentation fault (core dumped).Formalmente: comportamiento indefinido, que puede manifestarse como caída, corrupción o incluso “funcionar” y fallar mucho después, en otro punto del programa.
El programa no tiene forma de reaccionar: no hay recuperación posible una vez que se desreferenció el puntero nulo.
Fundamento en el estándar C11¶
C11 §7.22.3p1:
mallocdevuelve un puntero nulo si la asignación falla.C11 §7.22.3.1 / §7.22.3.2:
callocyrealloccomparten ese contrato de retorno.C11 §6.5.3.2p4: el operador unario
*requiere un puntero a un objeto; si es nulo, el comportamiento no está definido.La cátedra adopta esta comprobación como obligatoria porque un error de asignación manejado a tiempo se convierte en un valor de retorno, mientras que una desreferencia nula se convierte en una caída sin diagnóstico.
Corrección idiomática¶
❌ Código con el antipatrón¶
int *crear_vector(size_t n)
{
int *p = malloc(n * sizeof(*p));
for (size_t i = 0; i < n; i++) {
p[i] = 0;
}
return p;
}✅ Código refactorizado¶
int *crear_vector(size_t n)
{
int *p = malloc(n * sizeof(*p));
if (p == NULL) {
return NULL;
}
for (size_t i = 0; i < n; i++) {
p[i] = 0;
}
return p;
}La guarda se ubica inmediatamente después de la reserva y antes de todo
acceso; el llamador recibe NULL y decide qué hacer (regla 0x3009h).
⚠️ Casos límite¶
malloc(0)puede devolverNULLo un puntero válido pero no desreferenciable; no debe tratarse como error automático.realloctambién puede fallar; requiere el mismo tratamiento de valor de retorno antes de usar el puntero (ver0x3001hy0x3001h).Verificar
p != NULLdespués de usarpno sirve: la desreferencia previa ya disparó la UB (ver0x3001h).
Errores típicos al compilar o ejecutar¶
$ ./programa
Segmentation fault (core dumped)
# Con AddressSanitizer:
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
The signal is caused by a READ memory access.
#0 0x... in crear_vector programa.c:5Checklist de verificación¶
¿Cada
malloc,callocorealloctiene suif (p == NULL)?¿La guarda está antes del primer
*p,p[i]op->campo?¿La función propaga el fallo (
return NULL) en lugar de continuar?¿Documenté que la función puede retornar
NULL(regla 0x3009h)?
Reglas relacionadas¶
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — norma el chequeo inmediato del retorno.
0x3009h: Documentá explícitamente los casos en que una función puede retornar NULL — documentar los casos en que una función devuelve
NULL.0x2001h: Las funciones deben usar cláusulas de guarda y retornos anticipados para reducir la anidación profunda — la cláusula de guarda evita la anidación profunda.
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — mismo defecto en
realloc.0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — el fallo parcial deja fugas.
Antipatrón: Desreferencia inmediata tras realloc¶
Síntoma en el código del estudiante¶
El resultado de realloc se usa enseguida, sin preguntar si la reasignación
tuvo éxito:
ptr = realloc(ptr, nuevo_tam);
ptr[0] = 42;El acceso sigue en la línea inmediatamente posterior, sin ningún if.
Diagnóstico¶
Mecanismo del defecto¶
realloc comparte con malloc el contrato de retorno: devuelve NULL cuando
no puede satisfacer el nuevo tamaño. En esa línea, ptr recibe el puntero nulo
y la desreferencia siguiente (ptr[0] = 42) actúa sobre NULL: comportamiento
indefinido y, en la práctica, caída por SIGSEGV.
El defecto se agrava porque combina dos problemas: además de desreferenciar el
nulo, se perdió la referencia al bloque original (el realloc devolvió NULL y
el bloque viejo sigue vivo pero inaccesible), es decir, una fuga. Ese aspecto de
propiedad lo cubre 0x3015h.
Consecuencia observable¶
Caída con
Segmentation faulten la línea siguiente alrealloc.Fuga del bloque original si la reasignación falló.
Ninguna posibilidad de recuperarse: el error se materializa en el acto.
Fundamento en el estándar C11¶
C11 §7.22.3.5p3:
reallocdevuelve un puntero nulo si la asignación falla; el bloque previo no se libera.C11 §6.5.3.2p4: desreferenciar un puntero nulo es comportamiento indefinido.
La regla 0x3001h exige comprobar el retorno de toda asignación dinámica, incluida
realloc, antes de usarla.
Corrección idiomática¶
❌ Código con el antipatrón¶
int *agregar(int *ptr, size_t n)
{
ptr = realloc(ptr, n * sizeof(*ptr));
ptr[0] = 42;
return ptr;
}✅ Código refactorizado¶
int *agregar(int *ptr, size_t n)
{
int *tmp = realloc(ptr, n * sizeof(*tmp));
if (tmp == NULL) {
return ptr; /* conserva el bloque original */
}
ptr = tmp;
ptr[0] = 42;
return ptr;
}✅ Guarda explícita del caso nulo¶
int *tmp = realloc(ptr, n * sizeof(*tmp));
if (!tmp) {
return NULL;
}
ptr = tmp;
ptr[0] = 42;⚠️ Casos límite¶
No confundir
realloc(ptr, 0)con un fallo: puede devolverNULLporque el bloque se liberó, no porque no hubiera memoria.Si se decide abortar ante el fallo, hay que liberar
ptrexplícitamente (o devolverlo) para no filtrarlo.La verificación de
NULLdebe ir antes del primer uso; después ya no sirve (ver0x3001h).
Errores típicos al compilar o ejecutar¶
$ ./programa
Segmentation fault (core dumped)
# Con AddressSanitizer:
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
#0 0x... in agregar programa.c:3Checklist de verificación¶
¿Cada
realloctiene una comprobación deNULLantes del primer uso?¿Usé una variable temporal para no perder el bloque original?
¿Si falla, libero o devuelvo el bloque previo?
¿El primer acceso al puntero viene después de la guarda?
Reglas relacionadas¶
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — verificar el retorno de la asignación dinámica.
0x3015h: Reallocación segura: no sobreescribir el puntero original directamente — no sobreescribir el puntero original en
realloc.0x3009h: Documentá explícitamente los casos en que una función puede retornar NULL — documentar el retorno
NULL.0x3015h: Reallocación segura: no sobreescribir el puntero original directamente — la fuga asociada.
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — variante en una sola expresión.
Antipatrón: Asignación de retorno de malloc() a variable no puntero¶
Síntoma en el código del estudiante¶
El resultado de malloc se guarda en una variable entera:
int addr = malloc(sizeof(int));El identificador no lleva asterisco y su tipo es int, mientras que malloc
retorna una dirección.
Diagnóstico¶
Mecanismo del defecto¶
malloc devuelve void *. Asignarlo a un int sin cast viola las
restricciones de la asignación (§6.5.16.1): un puntero no es una expresión
aritmética. El compilador está obligado a emitir un diagnóstico; GCC y Clang lo
hacen con -Wint-conversion, que suele venir activado por -Wall.
Cuando el estudiante “arregla” el diagnóstico agregando un cast
(int addr = (int)malloc(...)), el problema se vuelve peor pero silencioso: en
una arquitectura de 64 bits la dirección tiene 64 bits y el int, 32. La
conversión trunca los 32 bits altos. addr guarda una dirección corrupta
que, al volver a convertirse en puntero, apunta a cualquier lado.
Consecuencia observable¶
Advertencia del compilador (ignorada con frecuencia).
Dirección truncada en 64 bits: el puntero reconstruido es inválido.
SIGSEGVal desreferenciar, lejos del origen del error.Si
intresulta del mismo ancho que el puntero en una plataforma, “funciona” y oculta el defecto, que reaparece al portar el código.
Fundamento en el estándar C11¶
C11 §6.5.16.1: solo se pueden asignar punteros a punteros compatibles o a
void *; asignar a un entero requiere cast explícito.C11 §6.3.2.3p5: la conversión entre puntero e entero es implementation-defined; el estándar no promete que preserve la dirección si el entero es más chico.
C11 §7.22.3p1:
mallocretornavoid *, nunca un entero.La regla 0x3001h refuerza que el valor de retorno debe guardarse como puntero y verificarse.
Corrección idiomática¶
❌ Código con el antipatrón¶
int addr = malloc(sizeof(int));
*(int *)addr = 42; /* dirección truncada y cast forzado */✅ Código refactorizado¶
int *ptr = malloc(sizeof(*ptr));
if (ptr == NULL) {
return NULL;
}
*ptr = 42;✅ Si de verdad se necesita una dirección numérica¶
#include <stdint.h>
int *ptr = malloc(sizeof(*ptr));
if (ptr == NULL) {
return NULL;
}
uintptr_t direccion = (uintptr_t)ptr; /* tipo capaz de contener un puntero */uintptr_t es el único entero garantizado para almacenar una dirección; aun
así, en la cátedra solo se admite con una justificación explícita.
⚠️ Casos límite¶
sizeof(int)puede coincidir consizeof(void *)en sistemas de 32 bits; eso no hace válida la conversión, solo la disimula.Guardar el retorno en
longtampoco es portable: en Windows 64 bitslongsigue siendo de 32 bits;intptr_t/uintptr_tsí.El cast a
intno elimina la advertencia en todos los compiladores; en Clang moderno el aviso por truncamiento se mantiene.
Errores típicos al compilar o ejecutar¶
$ gcc -Wall -Wextra -std=c11 programa.c
programa.c:3:15: warning: initialization of 'int' from 'void *' makes integer
from pointer without a cast [-Wint-conversion]
3 | int addr = malloc(sizeof(int));
$ ./programa
Segmentation fault (core dumped)Checklist de verificación¶
¿Toda variable que recibe el retorno de
malloces un puntero?¿El compilador no emite
-Wint-conversion?¿Nunca descarté una dirección con un cast a
int?¿Si necesito un entero para una dirección usé
uintptr_t?
Reglas relacionadas¶
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — guardar y verificar el retorno como puntero.
0x300Ah: Utilizá cast explícito al convertir tipos de punteros — conversiones de punteros explícitas.
0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr) —
sizeof(*ptr)en la reserva.0x300Ah: Utilizá cast explícito al convertir tipos de punteros — el camino inverso y simétrico.
Antipatrón: Comprobación de puntero nulo posterior a su desreferencia¶
Síntoma en el código del estudiante¶
El acceso ocurre primero y la pregunta por NULL después:
*ptr = 10;
if (ptr != NULL) {
/* ... */
}La verificación llegó tarde: está en la línea 2, pero el uso está en la 1.
Diagnóstico¶
Mecanismo del defecto¶
La comprobación de NULL solo tiene valor si protege al primer acceso. Si ptr
era nulo, la desreferencia de la línea anterior ya disparó comportamiento
indefinido: en la práctica, SIGSEGV. Cuando el flujo llega al if, o el
programa ya murió o el acceso “funcionó” por casualidad, y en ninguno de los
dos casos el if aporta protección.
Es habitual que este orden invierta aparezca combinado con una asignación sin
verificación (0x3001h): el estudiante escribe el uso, luego “por las dudas”
agrega el if, sin advertir que el daño ya está hecho. También puede aparecer
con el patrón if (ptr != NULL) { ... } else { ... } donde el uso indebido está
fuera del if.
Consecuencia observable¶
SIGSEGVen la línea anterior alif, lo que confunde al lector que ve una comprobación “presente”.Falsa sensación de robustez: el código parece defensivo sin serlo.
Si
ptrno era nulo, elifes redundante y solo agrega anidación.
Fundamento en el estándar C11¶
C11 §6.5.3.2p4: desreferenciar un puntero nulo es comportamiento indefinido; el orden de las sentencias importa.
C11 §6.8.4.1: las sentencias se ejecutan en secuencia; el
ifposterior no puede “retroactivamente” proteger un acceso previo.La regla 0x3001h exige que la comprobación sea inmediatamente posterior a la asignación dinámica y anterior a todo uso.
Corrección idiomática¶
❌ Código con el antipatrón¶
*ptr = 10;
if (ptr != NULL) {
printf("%d\n", *ptr);
}✅ Código refactorizado — guarda primero¶
if (ptr == NULL) {
return -1;
}
*ptr = 10;
printf("%d\n", *ptr);✅ Estructura con cláusula de guarda¶
int inicializar(int *ptr)
{
if (ptr == NULL) {
return -1;
}
*ptr = 10;
return 0;
}La guarda al inicio del bloque es el patrón que la regla 0x2001h prefiere: elimina la anidación y deja claro dónde empieza el camino feliz.
⚠️ Casos límite¶
No confundir con liberar y luego preguntar:
free(ptr); if (ptr) ...también es un uso posterior inválido (ver0x3002h).Si la función recibe un puntero que puede ser nulo, la precondición debe documentarse (regla 0x300Eh).
Preguntar por un campo (
ptr->sig != NULL) es distinto de preguntar porptr: lo primero requiere queptrya sea válido.
Errores típicos al compilar o ejecutar¶
$ ./programa
Segmentation fault (core dumped)
# Con AddressSanitizer:
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
#0 0x... in main programa.c:1
# la línea señalada es el uso, no el if posteriorChecklist de verificación¶
¿La comprobación de
NULLprecede a todo acceso al puntero?¿El uso no quedó fuera ni antes del
if?¿La guarda usa cláusula de retorno anticipado (regla 0x2001h)?
¿La precondición del parámetro está documentada (regla 0x300Eh)?
Reglas relacionadas¶
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — comprobar el retorno antes de usarlo.
0x2001h: Las funciones deben usar cláusulas de guarda y retornos anticipados para reducir la anidación profunda — cláusulas de guarda y retornos anticipados.
0x300Eh: Documentá explícitamente el comportamiento de las funciones al manejar punteros nulos como argumentos — documentar el comportamiento ante punteros nulos.
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — el caso general.
Antipatrón: Asignación múltiple a malloc en bucle sin liberación ante fallos parciales¶
Síntoma en el código del estudiante¶
En una reserva por filas, el chequeo de NULL dentro del bucle no libera lo ya
asignado:
for (int i = 0; i < n; i++) {
mat[i] = malloc(m * sizeof(int));
if (mat[i] == NULL) {
return NULL;
}
}Si falla la fila k, las filas 0..k-1 quedan sin dueño.
Diagnóstico¶
Mecanismo del defecto¶
Una matriz dinámica se construye con una reserva por fila. El bucle avanza asignando bloques independientes. Si una reserva intermedia falla, el retorno inmediato abandona el bucle, pero las reservas anteriores siguen vivas en el heap y ya nadie conserva sus punteros: son fugas irrecuperables.
La cantidad de memoria perdida crece con la cantidad de filas asignadas antes del fallo. En un programa que reintenta la operación, cada intento fallido filtra otro bloque completo. Peor: si el fallo se debió a falta de memoria, la fuga agrava la escasez.
Consecuencia observable¶
Fuga de memoria que no se manifiesta con pocos elementos.
Degradación progresiva del proceso hasta agotar la memoria.
Valgrind o LeakSanitizer reportan bloques “definitely lost” cuya pila de asignación señala el
mallocdel bucle.
Fundamento en el estándar C11¶
C11 §7.22.3.3: cada bloque reservado con
malloc/calloc/reallocdebe liberarse confree; no hay recolección automática de basura.C11 §7.22.3p1: ante el fallo,
mallocretornaNULLy no altera los bloques previos.La regla 0x300Fh complementa: la liberación de recursos anidados se hace en orden inverso al de asignación (de la última fila a la primera).
Corrección idiomática¶
❌ Código con el antipatrón¶
int **crear_matriz(size_t n, size_t m)
{
int **mat = malloc(n * sizeof(*mat));
if (mat == NULL) {
return NULL;
}
for (size_t i = 0; i < n; i++) {
mat[i] = malloc(m * sizeof(*mat[i]));
if (mat[i] == NULL) {
return NULL; /* fuga de mat[0..i-1] y de mat */
}
}
return mat;
}✅ Código refactorizado — limpieza en orden inverso¶
int **crear_matriz(size_t n, size_t m)
{
int **mat = malloc(n * sizeof(*mat));
if (mat == NULL) {
return NULL;
}
for (size_t i = 0; i < n; i++) {
mat[i] = malloc(m * sizeof(*mat[i]));
if (mat[i] == NULL) {
for (size_t j = 0; j < i; j++) {
free(mat[j]);
}
free(mat);
return NULL;
}
}
return mat;
}La liberación recorre las filas ya asignadas (j < i) y luego libera el arreglo
de punteros; ese orden inverso es el que exige la regla 0x300Fh.
✅ Alternativa — un único bloque contiguo¶
int *datos = malloc(n * m * sizeof(*datos));Con un solo malloc no hay fallo parcial posible; simplifica la propiedad y es
preferible cuando no hace falta que las filas sean intercambiables.
⚠️ Casos límite¶
Si
n == 0,malloc(0)puede devolverNULLsin ser un error real.La liberación posterior también debe ser simétrica: primero las filas y al final el arreglo de punteros.
Errores típicos al compilar o ejecutar¶
$ valgrind --leak-check=full ./programa
==1234== definitely lost: 320 bytes in 40 blocks
==1234== by 0x...: crear_matriz (programa.c:10)Checklist de verificación¶
¿Ante un fallo parcial libero todo lo asignado hasta el momento?
¿La liberación es en orden inverso al de asignación (regla 0x300Fh)?
¿Liberé también el arreglo de punteros de nivel superior?
¿Consideré usar un único bloque contiguo?
Reglas relacionadas¶
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — verificar cada asignación.
0x300Fh: Liberá la memoria en el orden inverso a su asignación — liberar en orden inverso.
0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes — anular punteros tras liberar.
0x3013h: Asignación de memoria con sizeof sobre puntero en lugar del tipo apuntado — error frecuente al dimensionar las filas.
Antipatrón: Desreferencia condicional de puntero local sin inicializar¶
Síntoma en el código del estudiante¶
Un puntero local se declara sin inicializar y se usa en una rama condicional:
int *p;
if (condicion) {
*p = 10;
}La variable no tiene valor conocido antes del uso.
Diagnóstico¶
Mecanismo del defecto¶
Una variable automática sin inicializar contiene basura: los bytes que
quedaron en esa posición de la pila del uso anterior. Para un puntero, ese valor
residual se interpreta como una dirección. Es un puntero salvaje (wild
pointer): no es NULL ni apunta a un objeto válido.
Desreferenciarlo escribe en una dirección arbitraria. Puede caer si la página no
está mapeada (SIGSEGV) o, peor, escribir dentro de una página válida y
corromper datos del propio programa. La rama if (condicion) no protege nada:
solo decide si el error ocurre.
Consecuencia observable¶
SIGSEGVcon una dirección aparentemente aleatoria.Corrupción de memoria silenciosa cuando la basura apunta a una zona válida.
El comportamiento cambia entre ejecuciones, porque el contenido residual de la pila depende de las llamadas previas.
Fundamento en el estándar C11¶
C11 §6.7.9p10: una variable automática sin inicializador tiene un valor indeterminado; leerla es comportamiento indefinido.
C11 §6.2.4p5: los objetos automáticos no se inicializan implícitamente (a diferencia de los estáticos, que sí se ponen en cero).
C11 §6.5.3.2p4: desreferenciar un puntero a un objeto inexistente es comportamiento indefinido.
La regla 0x7001h exige inicializar toda variable a un valor conocido; para punteros,
NULLes el valor canónico (regla 0x3008h).
Corrección idiomática¶
❌ Código con el antipatrón¶
int *p;
if (condicion) {
*p = 10;
}✅ Código refactorizado — reservar y verificar¶
int *p = NULL;
if (condicion) {
p = malloc(sizeof(*p));
if (p == NULL) {
return -1;
}
*p = 10;
}✅ Puntero a variable existente¶
int valor = 0;
int *p = NULL;
if (condicion) {
p = &valor; /* apunta a un objeto con vida conocida */
*p = 10;
}⚠️ Casos límite¶
Inicializar a
NULLes correcto, pero no exime de reservar antes de escribir.Un puntero global o
staticse inicializa en cero por el estándar; no es el caso de los locales.Inicializar no reemplaza la verificación del retorno de
malloc(regla 0x3001h).
Errores típicos al compilar o ejecutar¶
$ gcc -Wall -Wextra -std=c11 programa.c
programa.c:2:9: warning: 'p' is used uninitialized [-Wuninitialized]
$ ./programa
Segmentation fault (core dumped)Checklist de verificación¶
¿Todo puntero local se inicializa en su declaración (
= NULL)?¿Antes de escribir reservo memoria y verifico
NULL?¿Evité depender de valores residuales de la pila?
¿El compilador con
-Wall -Wextrano emite-Wuninitialized?
Reglas relacionadas¶
0x7001h: Siempre debés inicializar las variables a un valor conocido — inicializar variables a un valor conocido.
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — verificar el retorno de la reserva.
0x3008h: Los punteros nulos deben ser inicializados y comparados con NULL, no con 0 — usar
NULLpara punteros.0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — el
ifque llega tarde.
Antipatrón: Desreferencia directa tras retorno de realloc sin asignación temporal¶
Síntoma en el código del estudiante¶
El retorno de realloc se desreferencia en la misma expresión, sin variable
intermedia:
*((int *)realloc(p, n)) = 42;No hay forma de preguntar si la reasignación falló porque el puntero ni siquiera se guardó.
Diagnóstico¶
Mecanismo del defecto¶
La expresión anida tres operaciones: realloc, el cast a int * y la
desreferencia con *. Si realloc devuelve NULL (no pudo reasignar), el cast
convierte el puntero nulo y el * lo desreferencia: comportamiento indefinido y
caída inmediata.
Además, el valor de retorno se descarta después de la sentencia. La variable
p no se actualiza: si la reasignación movió el bloque a otra dirección, p
sigue apuntando al bloque viejo, que realloc ya liberó. El resultado es un
puntero colgante hacia memoria liberada, un use-after-free latente.
O sea, la misma línea concentra los dos defectos: desreferencia sin verificar y pérdida de la referencia actualizada.
Consecuencia observable¶
Si
reallocfalla:SIGSEGVinmediato.Si tiene éxito y movió el bloque:
pqueda colgante; cualquier uso posterior esuse-after-free.AddressSanitizer reporta el problema en la línea de la expresión.
Fundamento en el estándar C11¶
C11 §7.22.3.5p3:
reallocdevuelve un puntero nulo si falla y deja el bloque original intacto.C11 §7.22.3.5p2: si reasigna en un bloque nuevo, el viejo se libera.
C11 §6.5.3.2p4: desreferenciar el nulo es comportamiento indefinido.
La regla 0x3015h exige asignar a un temporal y verificar antes de usar; la regla 0x3001h exige la comprobación previa a todo uso.
Corrección idiomática¶
❌ Código con el antipatrón¶
*((int *)realloc(p, n * sizeof(int))) = 42;✅ Código refactorizado¶
int *tmp = realloc(p, n * sizeof(*tmp));
if (tmp == NULL) {
/* p conserva el bloque original */
return -1;
}
p = tmp;
*p = 42;✅ Variante con índice¶
int *tmp = realloc(p, n * sizeof(*tmp));
if (tmp == NULL) {
return -1;
}
p = tmp;
p[0] = 42;Guardar, verificar, actualizar y recién entonces usar: ese es el orden que materializa las reglas 0x3015h y 0x3001h juntas.
⚠️ Casos límite¶
El cast
(int *)alrededor derealloces redundante e innecesario en C (ver0x300Ah);reallocya devuelvevoid *.realloc(p, 0)puede devolverNULLsin ser un fallo de memoria; hay que distinguir ese caso por el valor den.Si se decide no continuar tras el fallo, el bloque original debe liberarse o devolverse para no filtrarlo.
Errores típicos al compilar o ejecutar¶
$ ./programa
Segmentation fault (core dumped)
# Con AddressSanitizer:
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
#0 0x... in main programa.c:1
# Si el bloque se movió y no se actualizó p:
ERROR: AddressSanitizer: heap-use-after-free on address 0x...Checklist de verificación¶
¿Reasigno
realloca un temporal antes de desreferenciar?¿Verifiqué
tmp == NULLantes de usarlo?¿Actualicé
pcon el bloque nuevo tras el éxito?¿El caso de fallo deja el bloque original accesible o liberado?
Reglas relacionadas¶
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — verificar el retorno antes de usarlo.
0x3015h: Reallocación segura: no sobreescribir el puntero original directamente — usar temporal en
realloc.0x300Ah: Utilizá cast explícito al convertir tipos de punteros — evitar casts redundantes.
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — misma desreferencia, en sentencia separada.
0x3015h: Reallocación segura: no sobreescribir el puntero original directamente — la pérdida del bloque original.