Regla 0x3013h: Asignación de memoria con sizeof sobre puntero en lugar del tipo apuntado
Memoria, punteros y tipos (0x30XX)
0x3013h: Asignación de memoria con sizeof sobre puntero en lugar del tipo apuntado¶
Enunciado normativo¶
Al reservar memoria para un puntero
ptr, el tamaño DEBE calcularse consizeof(*ptr)osizeof(tipo_apuntado). NO DEBE usarsesizeof(ptr), que mide el puntero y no el objeto al que apunta.
¿Por qué existe esta regla?¶
El problema¶
ptr es un puntero; *ptr es el objeto apuntado. Sus tamaños son distintos: un
puntero ocupa 4 u 8 bytes según la plataforma, mientras que el objeto puede
ocupar más o menos. malloc(n * sizeof(ptr)) reserva n * 8 bytes y luego el
código escribe n objetos de sizeof(*ptr) bytes cada uno.
Si sizeof(*ptr) > sizeof(ptr) —lo habitual para structs— la escritura desborda
el bloque. Si es menor, se desperdicia memoria. En ambos casos el error es
silencioso: malloc devuelve un bloque perfectamente válido del tamaño
equivocado.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Desbordamiento de heap | Escribir más allá del bloque corrompe el asignador. |
| Bug silencioso | El programa funciona con pocos elementos y falla al crecer. |
| Desperdicio | Un tamaño mayor al necesario oculta el error de cálculo. |
| Seguridad | El desbordamiento de heap puede ser explotable. |
Fundamento en el estándar y en la cátedra¶
C11 §6.5.3.4 da el tamaño del tipo del operando; ptr y *ptr tienen tipos
distintos y por lo tanto tamaños distintos. La presente regla es el caso
patológico que 0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr) previene al preferir sizeof(*ptr): aquí el
tamaño usado corresponde al puntero, no al objeto. La cátedra exige que la
expresión de tamaño mencione el objeto reservado.
Alcance y excepciones¶
Aplica a toda reserva con malloc, calloc y realloc de un puntero. No
prohíbe reservar memoria para un arreglo de punteros: allí el objeto apuntado
es un puntero y sizeof(*pp) es correcto (por ejemplo, char **filas = malloc(n * sizeof(*filas)), donde *filas es un char *). La clave es que
sizeof mida lo que se va a almacenar, no la variable receptora por descuido.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — sizeof del puntero en un arreglo de structs¶
struct punto
{
double x;
double y;
};
struct punto *puntos = malloc(10 * sizeof(puntos));Por qué falla: sizeof(puntos) es el tamaño del puntero (8 bytes en 64 bits),
no el de struct punto (16 bytes). La reserva alcanza para 80 bytes, pero el
programa cree tener 10 puntos que ocupan 160 bytes. Desde el sexto elemento,
cada escritura desborda el bloque.
❌ Contraejemplo 2 — calloc con tamaño equivocado¶
int *valores = calloc(100, sizeof(valores));
for (size_t i = 0; i < 100; i++)
{
valores[i] = (int)i;
}Por qué falla: sizeof(valores) mide el puntero, no el int. Se reservan
100 * 8 = 800 bytes cuando se necesitaban 400 (suponiendo int de 4 bytes).
No hay desbordamiento en este caso, pero el desperdicio es del doble y el error
de razonamiento queda oculto. La forma correcta es sizeof(*valores).
✅ Ejemplo conforme 1 — sizeof del objeto apuntado¶
int *valores = malloc(100 * sizeof(*valores));
if (valores == NULL)
{
return -1;
}sizeof(*valores) es sizeof(int). Si el tipo cambia a long, la reserva se
ajusta sola. La cantidad (100) y el tamaño unitario quedan ambos expresados y la
verificación de 0x3001h: Siempre verificá la asignación exitosa de memoria dinámica cierra el patrón.
✅ Ejemplo conforme 2 — Arreglo de punteros: sizeof es correcto¶
char **filas = malloc(cantidad * sizeof(*filas));
if (filas == NULL)
{
return NULL;
}
for (size_t i = 0; i < cantidad; i++)
{
filas[i] = malloc(longitud_fila + 1);
}Acá el objeto apuntado por filas es un char *; sizeof(*filas) mide ese
puntero, que es exactamente lo que se guarda en el arreglo. Es el uso legítimo de
sizeof con un puntero y no cae bajo esta regla.
⚠️ Casos límite¶
Puntero a puntero: distinguí si se reserva un arreglo de punteros (
sizeof(*pp)= tamaño puntero) o un arreglo de objetos (sizeof(**pp)).Desbordamiento del producto: aun con el
sizeofcorrecto,n * sizeof(*p)puede excedersize_t.realloc: al reasignar, usá el mismo criterio y una variable temporal conforme a 0x3015h: Reallocación segura: no sobreescribir el puntero original directamente.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Patrón malloc(... sizeof(ptr)) donde sizeof recibe el puntero receptor. |
gcc / clang | gcc -Wall -Wextra -std=c11 -fanalyzer ... | dereference of possibly-NULL y out-of-bounds en la escritura. |
cppcheck | cppcheck --enable=all archivo.c | Allocation size mismatch entre el tipo y sizeof. |
Checklist de autocontrol¶
¿El
sizeofrecibe*ptry noptr?¿Reservo un arreglo de objetos o de punteros? ¿Coincide con el tipo?
¿Validé el producto
n * sizeof(*ptr)contra desbordamiento?¿Verifiqué el retorno de la reserva?
¿La expresión menciona el objeto que se va a almacenar?
Reglas relacionadas¶
0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr) — regla general: siempre
sizeof, preferentementesizeof(*ptr).0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — la reserva se verifica contra
NULL.0x300Ch: Verificá siempre los límites de los arreglos antes de acceder a sus elementos — un tamaño correcto no excusa validar los índices.
0x3015h: Reallocación segura: no sobreescribir el puntero original directamente — el mismo cuidado en la reasignación con
realloc.
Antipatrón: Uso de sizeof(puntero) en reserva dinámica¶
Síntoma en el código del estudiante¶
La reserva usa sizeof sobre la variable puntero en lugar del tipo
apuntado:
nodo_t *n = malloc(sizeof(n));
int *v = malloc(10 * sizeof(v));El error se ve a simple vista porque el argumento de sizeof es el mismo
identificador que está a la izquierda del =.
Diagnóstico¶
Mecanismo del defecto¶
sizeof(ptr) mide el tamaño del puntero, no el del objeto apuntado. En una
arquitectura de 64 bits eso son 8 bytes, cualquiera sea el tipo apuntado. Si la
estructura nodo_t ocupa 32 bytes, malloc(sizeof(n)) reserva solo 8: el
bloque es demasiado pequeño.
Escribir n->dato = x o v[i] = x escribe fuera de los límites del bloque
reservado. Eso es un desbordamiento de heap (heap buffer overflow): se
pisan los metadatos del asignador o datos de otro bloque. En C no hay
verificación de límites, por lo que el error es silencioso hasta que corrompe lo
suficiente como para caer.
Consecuencia observable¶
Escrituras fuera de rango que corrompen el heap.
malloc(): corrupted top sizeofree(): invalid next size (fast)englibc.Caídas erráticas lejos del punto que originó el daño, lo que lo vuelve muy difícil de localizar sin un detector.
Fundamento en el estándar C11¶
C11 §6.5.3.4:
sizeofaplicado a una expresión da el tamaño de su tipo;ntiene tiponodo_t *, así que devuelve el tamaño del puntero.C11 §7.22.3p1:
mallocreserva exactamente la cantidad de bytes pedida; no redondea al tamaño del objeto que el programador tenía en mente.La forma correcta según la regla 0x300Bh es
sizeof(*ptr), que mide el tipo apuntado y se adapta a refactorizaciones. Este defecto se solapa con la regla 0x3013h.
Corrección idiomática¶
❌ Código con el antipatrón¶
nodo_t *n = malloc(sizeof(n));
if (n == NULL) {
return NULL;
}
n->dato = 42; /* escribe fuera de los 8 bytes reservados */✅ Código refactorizado¶
nodo_t *n = malloc(sizeof(*n));
if (n == NULL) {
return NULL;
}
n->dato = 42;✅ Arreglo con sizeof(*v)¶
int *v = malloc(10 * sizeof(*v));
if (v == NULL) {
return NULL;
}
for (size_t i = 0; i < 10; i++) {
v[i] = (int)i;
}⚠️ Casos límite¶
char *s = malloc(sizeof(char) * (n + 1))es válido porque el cast no está y el tamaño es explícito; el problema essizeof(puntero), nosizeof(tipo).malloc(sizeof(n))dondenes un arreglo (no un puntero) sí da el tamaño total; confundir ambos casos es frecuente al refactorizar parámetros (ver0x300Bh).En
calloc(n, sizeof(*p))el error simétrico essizeof(p).
Errores típicos al compilar o ejecutar¶
$ ./programa
malloc(): corrupted top size
Aborted (core dumped)
# Con AddressSanitizer:
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
WRITE of size 8 at 0x... thread T0Checklist de verificación¶
¿Toda reserva usa
sizeof(*ptr)osizeof(tipo)?¿Ningún
malloc/callocpasasizeofde la variable puntero?¿La cantidad de elementos multiplica al tamaño de un elemento?
¿Corrí el programa con AddressSanitizer para descartar desbordes?
Reglas relacionadas¶
0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr) — usar
sizeof(*ptr)en la reserva.0x3013h: Asignación de memoria con sizeof sobre puntero en lugar del tipo apuntado — reserva con
sizeofsobre el puntero en lugar del tipo.0x300Fh: Liberá la memoria en el orden inverso a su asignación — liberar en orden inverso (norma asociada).
0x3016h: Orden incorrecto o sospechoso de argumentos en llamadas a memset — mismo error en
memset.