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 0x3016h: Orden incorrecto o sospechoso de argumentos en llamadas a memset

Memoria, punteros y tipos (0x30XX)

Universidad Nacional de Río Negro

0x3016h: Orden incorrecto o sospechoso de argumentos en llamadas a memset

Enunciado normativo

En toda llamada a memset, los argumentos DEBEN respetar el orden canónico memset(ptr, valor, cantidad): primero la dirección, después el valor del byte a escribir y por último la cantidad de bytes. NO DEBE intercambiarse el valor con la cantidad.

¿Por qué existe esta regla?

El problema

memset tiene tres argumentos de tipos distintos pero fáciles de confundir: void *, int y size_t. El segundo es el valor del byte (se convierte a unsigned char); el tercero es cuántos bytes se escriben. Invertirlos pasa desapercibido porque ambos son números: memset(buffer, sizeof(buffer), 0) es sintácticamente válido.

El efecto es doble y catastrófico: no se inicializa la memoria (se escribe el valor sizeof(buffer) en 0 bytes, es decir nada) y, si la cantidad era grande, podría escribirse un valor equivocado en una región enorme. El error es un clásico pedagógico porque compila sin advertencias.

Consecuencias de violarla

Tipo de consecuenciaEfecto concreto
No inicializaLa memoria conserva basura; los bugs por datos sin inicializar aparecen después.
Escritura masivaCon argumentos invertidos y valores grandes, se corrompe memoria adyacente.
Bug silenciosoEl código parece inicializar pero el efecto real es nulo.
MantenibilidadEl lector debe recordar el orden; conviene que sea explícito.

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

C11 §7.24.6.1 define memset(void *s, int c, size_t n). La cátedra adopta el orden como parte del estilo idiomático y lo relaciona con 0x3018h: Inicialización idiomática de agregados con {0} en lugar de memset inmediato: para inicializar agregados a cero, = {0} es preferible a un memset inmediato. La regla tiene corrección automática porque el patrón invertido es detectable de forma fiable.

Alcance y excepciones

Aplica a memset y a funciones con firma análoga (memcpy, memmove, bzero), donde el tamaño es el último argumento. No aplica a calloc, cuyo orden es calloc(cantidad, tamano), ni a memset legítimo para rellenar con bytes distintos de cero. Tampoco exime de verificar que la cantidad sea correcta.

Ejemplos exhaustivos

❌ Contraejemplo 1 — Cantidad y valor invertidos

struct config_t config;
memset(&config, sizeof(config), 0);

Por qué falla: sizeof(config) es el valor del byte a escribir y 0 es la cantidad de bytes. memset no escribe nada, porque el tercer argumento es 0. El struct queda sin inicializar y el error no produce ninguna advertencia.

❌ Contraejemplo 2 — Invertidos en un arreglo

int datos[100];
memset(datos, sizeof(datos), 0);

Por qué falla: igual que el anterior, no inicializa. Peor aún, si la intención fuera inicializar a un valor distinto de cero, el valor se tomaría de sizeof(datos) y la cantidad de 0, de modo que el resultado sería silenciosamente nulo. La forma idiomática es dejar el valor en el segundo lugar.

✅ Ejemplo conforme 1 — Orden canónico explícito

unsigned char buffer[256];
memset(buffer, 0, sizeof(buffer));

El segundo argumento es el valor (0) y el tercero la cantidad (sizeof(buffer)). La memoria se inicializa por completo y el tamaño se mantiene correcto si el arreglo cambia de dimensión. El orden de los argumentos coincide con el de la firma estándar.

✅ Ejemplo conforme 2 — Inicialización de un struct con {0}

struct config_t config = {0};

Para llevar un agregado a cero, la forma idiomática es {0} conforme a 0x3018h: Inicialización idiomática de agregados con {0} en lugar de memset inmediato. memset se reserva para rellenar con bytes distintos de cero o para una región que no puede inicializarse en su declaración. Si se usa memset, el orden canónico sigue siendo obligatorio.

⚠️ Casos límite

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cmemset(ptr, sizeof(...), ...) con el tamaño en el segundo argumento.
gcc / clanggcc -Wall -Wextra -std=c11 -Wsizeof-pointer-memaccess ...memset con argumento de tamaño sospechoso.
Revisión manualSegundo argumento grande o tercero igual a 0/1.

Checklist de autocontrol

Reglas relacionadas

Antipatrón: Tamaño insuficiente en memset con sizeof(ptr)

Síntoma en el código del estudiante

Después de reservar una estructura, se inicializa con memset pero el tamaño sale de sizeof sobre el puntero:

struct nodo_t *n = malloc(sizeof(*n));
memset(n, 0, sizeof(n));

El tercer argumento repite el error de 0x3013h: n es un puntero.

Diagnóstico

Mecanismo del defecto

memset(destino, valor, cantidad) escribe exactamente cantidad bytes a partir de destino. Si cantidad es sizeof(n) —el tamaño del puntero, 8 bytes— solo se limpian los primeros 8 bytes del bloque, sin importar cuántos mida la estructura. Los campos que caen más allá del byte 8 quedan con basura.

Ese resto sin inicializar es el peor de los casos: contiene valores residuales del heap (direcciones viejas, contadores, longitudes). Al usarlos como índices o punteros, el programa se comporta de manera impredecible. El error es silencioso porque memset no puede saber cuánta memoria había reservada.

Consecuencia observable

Fundamento en el estándar C11

Corrección idiomática

❌ Código con el antipatrón
struct nodo_t *n = malloc(sizeof(*n));
if (n == NULL) {
    return NULL;
}
memset(n, 0, sizeof(n));   /* limpia solo 8 bytes */
✅ Código refactorizado — sizeof(*n)
struct nodo_t *n = malloc(sizeof(*n));
if (n == NULL) {
    return NULL;
}
memset(n, 0, sizeof(*n));
✅ Alternativa idiomática — inicialización con {0}
struct nodo_t *n = malloc(sizeof(*n));
if (n == NULL) {
    return NULL;
}
*n = (struct nodo_t){0};

La regla 0x3018h prefiere {0} para agregados declarados; aplicada a memoria dinámica, la asignación compuesta evita depender de un sizeof repetido.

⚠️ Casos límite

Errores típicos al compilar o ejecutar

# Sin sanitizadores el error es invisible:
$ ./programa
# el campo n->sig contiene basura y el recorrido cae lejos del origen
Segmentation fault (core dumped)

$ valgrind ./programa
==1234== Conditional jump or move depends on uninitialised value(s)

Checklist de verificación

Reglas relacionadas