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 0x3013h: Asignación de memoria con sizeof sobre puntero en lugar del tipo apuntado

Memoria, punteros y tipos (0x30XX)

Universidad Nacional de Río Negro

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 con sizeof(*ptr) o sizeof(tipo_apuntado). NO DEBE usarse sizeof(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 consecuenciaEfecto concreto
Desbordamiento de heapEscribir más allá del bloque corrompe el asignador.
Bug silenciosoEl programa funciona con pocos elementos y falla al crecer.
DesperdicioUn tamaño mayor al necesario oculta el error de cálculo.
SeguridadEl 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

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cPatrón malloc(... sizeof(ptr)) donde sizeof recibe el puntero receptor.
gcc / clanggcc -Wall -Wextra -std=c11 -fanalyzer ...dereference of possibly-NULL y out-of-bounds en la escritura.
cppcheckcppcheck --enable=all archivo.cAllocation size mismatch entre el tipo y sizeof.

Checklist de autocontrol

Reglas relacionadas

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

Fundamento en el estándar C11

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

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 T0

Checklist de verificación

Reglas relacionadas