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 0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr)

Memoria, punteros y tipos (0x30XX)

Universidad Nacional de Río Negro

0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr)

Enunciado normativo

Toda reserva dinámica DEBE calcular su tamaño con el operador sizeof, DEBE incluir la cantidad de elementos cuando corresponda, y DEBE preferir la forma sizeof(*ptr) sobre sizeof(tipo).

¿Por qué existe esta regla?

El problema

Un tamaño escrito a mano (4, 8, 100) es una suposición sobre la plataforma que el compilador no puede verificar. sizeof obtiene el tamaño del tipo en la unidad de traducción actual y se mantiene correcto aunque el tipo cambie.

La forma sizeof(*ptr) es superior a sizeof(tipo) porque ata el tamaño al puntero que se está reservando. Si mañana ptr cambia de int * a long *, o de nodo_t * a nodo_v2_t *, la reserva sigue siendo correcta sin editar la expresión. Con sizeof(tipo) hay que recordar actualizar los dos lados.

Consecuencias de violarla

Tipo de consecuenciaEfecto concreto
Desbordamiento de heapUn tamaño menor al necesario corrompe memoria al escribir.
DesperdicioUn tamaño mayor reserva de más y oculta errores de cálculo.
Bug silenciosoEl código funciona en una plataforma y falla al portarlo.
MantenibilidadCambiar el tipo del puntero obliga a rastrear todos los sizeof.

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

C11 §6.5.3.4 define sizeof como una expresión constante cuando el tipo es completo. La cátedra adopta sizeof(*ptr) como forma idiomática porque combina verificación de tipos y resistencia a refactorizaciones, y complementa a 0x3013h: Asignación de memoria con sizeof sobre puntero en lugar del tipo apuntado, que prohíbe específicamente usar el tamaño del puntero en lugar del apuntado.

Alcance y excepciones

Aplica a malloc, calloc, realloc y a arreglos de longitud calculada. No obliga a sizeof cuando el tamaño proviene de un cálculo semántico ya expresado correctamente (por ejemplo strlen(s) + 1), aunque ese cálculo debe ser correcto. calloc recibe la cantidad y el tamaño; la misma preferencia aplica al segundo argumento.

Ejemplos exhaustivos

❌ Contraejemplo 1 — Constante mágica en lugar de sizeof

int *valores = malloc(40);
if (valores == NULL)
{
    return -1;
}
valores[0] = 1;

Por qué falla: 40 asume sizeof(int) == 4, lo que no es universal. En una plataforma con int de 2 bytes se reservan 20 enteros cuando el programa cree tener diez; en otra con int de 8, sólo cinco. La cantidad de elementos y el tipo deben expresarse, no adivinarse.

❌ Contraejemplo 2 — sizeof(tipo) desincronizado tras el cambio de tipo

long *valores = malloc(10 * sizeof(int));

Por qué falla: la reserva usa sizeof(int) pero el puntero es long *. Si long ocupa más que int, se reservan menos bytes de los que se escribirán: desbordamiento de heap. La forma sizeof(*valores) habría seguido al tipo sin intervención.

✅ Ejemplo conforme 1 — Forma idiomática sizeof(*ptr)

int *valores = malloc(10 * sizeof(*valores));
if (valores == NULL)
{
    return -1;
}

La cantidad (10) expresa la intención y sizeof(*valores) el tamaño unitario. Si el tipo del puntero cambia, la reserva permanece correcta. La verificación de 0x3001h: Siempre verificá la asignación exitosa de memoria dinámica completa el patrón.

✅ Ejemplo conforme 2 — Cadena con terminador y struct dinámico

char *copia = malloc(strlen(origen) + 1);
if (copia == NULL)
{
    return NULL;
}
strcpy(copia, origen);

Se reserva strlen más el byte nulo. El + 1 es parte del cálculo semántico y debe estar presente; omitirlo es 0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr). Para un struct con arreglo flexible, sumá además n * sizeof(elemento), como advierte 0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr).

⚠️ Casos límite

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cmalloc/calloc/realloc con tamaño literal o sin sizeof.
gcc / clanggcc -Wall -Wextra -std=c11 ...No detecta la constante mágica; -fanalyzer sí el desbordamiento.
cppcheckcppcheck --enable=all archivo.cAllocation size mismatch entre tipo y sizeof.

Checklist de autocontrol

Reglas relacionadas

Antipatrón: Pointer decay en sizeof de arreglo parámetro

Síntoma en el código del estudiante

La función recibe un arreglo e intenta calcular su cantidad con sizeof:

void imprimir(int vec[])
{
    size_t n = sizeof(vec) / sizeof(vec[0]);
    for (size_t i = 0; i < n; i++) {
        printf("%d\n", vec[i]);
    }
}

El estudiante cree que sizeof(vec) mide el arreglo, pero vec es un parámetro y el compilador ya lo trató como puntero.

Diagnóstico

Mecanismo del defecto

En casi toda expresión, el nombre de un arreglo decae (decay) a un puntero a su primer elemento (§6.3.2.1p3). Al declarar un parámetro int vec[], el compilador lo ajusta a int *vec (§6.7.6.3p7). Por eso sizeof(vec) devuelve sizeof(int *) —8 bytes en 64 bits— y no el tamaño del arreglo original.

El cociente sizeof(vec) / sizeof(vec[0]) da entonces 8 / 4 = 2, sin importar cuántos elementos se hayan pasado. El lazo recorre solo 2 elementos y el resto queda sin procesar. En otros tipos el cociente puede dar 1 o directamente truncarse.

Consecuencia observable

Fundamento en el estándar C11

Corrección idiomática

❌ Código con el antipatrón
void imprimir(int vec[])
{
    size_t n = sizeof(vec) / sizeof(vec[0]);
    for (size_t i = 0; i < n; i++) {
        printf("%d\n", vec[i]);
    }
}
✅ Código refactorizado — cantidad explícita
void imprimir(const int *vec, size_t n)
{
    for (size_t i = 0; i < n; i++) {
        printf("%d\n", vec[i]);
    }
}
✅ El sizeof que sí funciona — dentro del alcance del arreglo
int main(void)
{
    int datos[5] = {1, 2, 3, 4, 5};
    size_t n = sizeof(datos) / sizeof(datos[0]);   /* 5, correcto */
    imprimir(datos, n);
    return 0;
}

datos no decae aquí porque sizeof es una de las excepciones; el largo se calcula en el mismo ámbito donde el arreglo es un arreglo.

⚠️ Casos límite

Errores típicos al compilar o ejecutar

# Compila sin advertencia; el error es de lógica:
$ ./programa
1
2
# se imprimen solo 2 elementos de 5

$ gcc -Wall -Wextra -std=c11 programa.c
# sin diagnóstico: el decay es legal

Checklist de verificación

Reglas relacionadas

Antipatrón: Reserva de buffer con malloc(strlen(s)) sin espacio para byte nulo

Síntoma en el código del estudiante

El buffer para copiar una cadena se reserva con el largo exacto que reporta strlen:

char *dup = malloc(strlen(s));
strcpy(dup, s);

El + 1 que debería acompañar a strlen no está.

Diagnóstico

Mecanismo del defecto

strlen cuenta los caracteres hasta, sin incluir, el byte nulo '\0'. Una cadena "dato" tiene strlen igual a 4, pero ocupa 5 bytes: d, a, t, o y '\0'. Al reservar strlen(s) bytes, falta lugar para el terminador.

strcpy copia los 4 caracteres y el '\0', es decir 5 bytes en un bloque de 4. Es un off-by-one de escritura: se escribe un byte fuera del bloque reservado. Ese byte pisa metadatos del asignador o el comienzo de otro bloque. Además, la cadena resultante queda sin terminador si por casualidad el byte siguiente no era cero.

Consecuencia observable

Fundamento en el estándar C11

Corrección idiomática

❌ Código con el antipatrón
char *duplicar(const char *s)
{
    char *dup = malloc(strlen(s));
    if (dup == NULL) {
        return NULL;
    }
    strcpy(dup, s);
    return dup;
}
✅ Código refactorizado
char *duplicar(const char *s)
{
    char *dup = malloc((strlen(s) + 1) * sizeof(*dup));
    if (dup == NULL) {
        return NULL;
    }
    strcpy(dup, s);
    return dup;
}

El + 1 es para el terminador; sizeof(*dup) deja el cálculo preparado para cualquier tipo de carácter.

✅ Alternativa robusta con memcpy
size_t largo = strlen(s) + 1;
char *dup = malloc(largo * sizeof(*dup));
if (dup == NULL) {
    return NULL;
}
memcpy(dup, s, largo);
⚠️ Casos límite

Errores típicos al compilar o ejecutar

# Con AddressSanitizer:
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
WRITE of size 1 at 0x... thread T0
#0 0x... in __interceptor_strcpy
#1 0x... in duplicar programa.c:7

# Con glibc:
$ ./programa
malloc(): corrupted top size
Aborted (core dumped)

Checklist de verificación

Reglas relacionadas

Antipatrón: Cálculo erróneo de tamaño para struct dinámico con miembro flexible

Síntoma en el código del estudiante

La reserva de una estructura con arreglo flexible suma la cantidad de elementos sin multiplicar por el tamaño del elemento:

malloc(sizeof(struct vector) + 10);

El 10 parece contar elementos, pero malloc cuenta bytes.

Diagnóstico

Mecanismo del defecto

Un miembro de arreglo flexible (FAM) se declara como último campo con corchetes vacíos:

struct vector {
    size_t cantidad;
    int datos[];
};

sizeof(struct vector) incluye todo menos el arreglo flexible. Para n elementos hay que sumar n * sizeof(elemento). Si el elemento mide más de un byte —int mide 4, un puntero mide 8—, sizeof(struct) + n reserva de menos: + 10 suma 10 bytes en lugar de 40.

El resultado es un desbordamiento de heap al escribir el elemento 3 o 4 (a partir de los 10 bytes). Se pisan los metadatos del asignador o el bloque siguiente, con el desenlace habitual: corrupción y aborto tardío.

Consecuencia observable

Fundamento en el estándar C11

Corrección idiomática

❌ Código con el antipatrón
struct vector {
    size_t cantidad;
    int datos[];
};

struct vector *crear(size_t n)
{
    struct vector *v = malloc(sizeof(struct vector) + n);
    if (v == NULL) {
        return NULL;
    }
    v->cantidad = n;
    for (size_t i = 0; i < n; i++) {
        v->datos[i] = 0;   /* escribe fuera de rango */
    }
    return v;
}
✅ Código refactorizado
struct vector *crear(size_t n)
{
    struct vector *v = malloc(sizeof(*v) + n * sizeof(v->datos[0]));
    if (v == NULL) {
        return NULL;
    }

    v->cantidad = n;
    for (size_t i = 0; i < n; i++) {
        v->datos[i] = 0;
    }
    return v;
}

El tamaño queda expresado en función de sizeof, así que sigue siendo correcto si el tipo de datos cambia.

⚠️ Casos límite

Errores típicos al compilar o ejecutar

# Con AddressSanitizer:
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
WRITE of size 4 at 0x... thread T0
#0 0x... in crear programa.c:15
# Con glibc:
$ ./programa
malloc(): corrupted top size
Aborted (core dumped)

Checklist de verificación

Reglas relacionadas