Regla 0x3018h: Inicialización idiomática de agregados con {0} en lugar de memset inmediato
Memoria, punteros y tipos (0x30XX)
0x3018h: Inicialización idiomática de agregados con {0} en lugar de memset inmediato¶
Enunciado normativo¶
Las variables locales de tipo
structo arreglo que deban quedar en cero DEBEN inicializarse en su declaración con= {0}. NO DEBE declararse la variable sin inicializar y emitir a continuación unmemsetpara llevarla a cero.
¿Por qué existe esta regla?¶
El problema¶
Declarar una variable y luego inicializarla con memset separa dos hechos que
deberían ser uno: la variable existe, y existe sin un valor definido durante un
instante. En ese intervalo, cualquier acceso accidental lee basura. Además, el
memset puede escribirse mal (ver 0x3016h: Orden incorrecto o sospechoso de argumentos en llamadas a memset) o puede olvidarse por completo
en un camino de error.
La inicialización en la declaración elimina el estado intermedio: no hay ningún punto del código donde la variable sea observable sin un valor conocido. Es más corta, más difícil de omitir y el compilador la entiende como parte de la declaración.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Estado transitorio | Entre la declaración y el memset la variable contiene basura. |
| Bug silencioso | Si el memset se omite en una rama, los campos quedan sin inicializar. |
| Ruido | Dos sentencias donde alcanza una; más código que revisar. |
| Errores de argumentos | El memset mal escrito no inicializa y nadie lo nota. |
Fundamento en el estándar y en la cátedra¶
C11 §6.7.9 permite inicializar agregados con una lista entre llaves; si se da
menos inicializadores que elementos, el resto se inicializa a cero. Por eso
{0} lleva todos los campos a cero. La cátedra prefiere esta forma sobre
memset por claridad y por la regla de inicialización de 0x7001h: Siempre debés inicializar las variables a un valor conocido. El
memset queda para rellenar con bytes distintos de cero o para regiones ya
existentes.
Alcance y excepciones¶
Aplica a agregados locales (arreglos y structs) que deban quedar en cero.
No aplica a memoria del heap: un bloque reservado con malloc no puede
inicializarse en la declaración; allí se usa calloc o un memset posterior con
el orden correcto. Tampoco prohíbe memset para rellenar con un patrón distinto
de cero (0xFF, 'A').
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Declarar y limpiar con memset¶
struct punto_t punto;
memset(&punto, 0, sizeof(punto));
punto.x = 1.0;Por qué falla: entre la declaración y el memset, punto contiene basura; si
una refactorización mueve código entre esas líneas, aparece un bug por dato sin
inicializar. La forma idiomática lo expresa en una sola línea.
❌ Contraejemplo 2 — Arreglo del que se olvida el memset¶
int acumuladores[10];
if (modo_especial)
{
memset(acumuladores, 0, sizeof(acumuladores));
}
acumuladores[0] += valor;Por qué falla: el memset está dentro de una rama condicional, de modo que en
el camino normal los acumuladores quedan sin inicializar y la suma parte de
basura. Inicializar en la declaración elimina la posibilidad de olvidar la rama.
✅ Ejemplo conforme 1 — Inicialización en la declaración¶
struct punto_t punto = {0};
int acumuladores[10] = {0};
punto.x = 1.0;
acumuladores[0] += valor;Ambas variables tienen un valor conocido desde el instante en que existen. No hay sentencia separada que pueda omitirse y el compilador inicializa a cero todos los elementos no nombrados.
✅ Ejemplo conforme 2 — Memoria del heap con calloc¶
int *valores = calloc(cantidad, sizeof(*valores));
if (valores == NULL)
{
return NULL;
}Cuando la memoria viene del heap, calloc inicializa en cero y a la vez
verifica el desbordamiento del producto. Si se usara malloc, correspondería
memset(valores, 0, cantidad * sizeof(*valores)) con el orden canónico y la
verificación de 0x3001h: Siempre verificá la asignación exitosa de memoria dinámica. {0} no puede aplicarse a memoria reservada
dinámicamente.
⚠️ Casos límite¶
Inicialización parcial a un valor:
struct punto_t p = {1.0, 2.0};nombra los primeros campos y pone el resto en cero; es válido y no es “medio inicializado”.Variables estáticas: ya están garantizadas en cero por C11 §6.7.9;
= {0}es redundante pero inocuo y a veces explícito.= {0}y el primer elemento: en un arreglo, el0correponde al primer elemento y el resto se completa con ceros; no es un inicializador especial.Structs con arreglos flexibles: la inicialización con
{0}no reserva el arreglo flexible; hay que sumar su tamaño (ver 0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr)).
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Declaración de agregado local seguida de memset a cero. |
gcc / clang | gcc -Wall -Wextra -Wuninitialized -std=c11 ... | may be used uninitialized si falta la inicialización. |
| Revisión manual | — | memset(&x, 0, sizeof(x)) inmediatamente después de declarar x. |
Checklist de autocontrol¶
¿Inicialicé los agregados locales en su declaración?
¿Usé
= {0}y no unmemsetinmediato?¿La memoria del heap usa
calloco unmemsetbien ordenado?¿El
memsetque queda rellena con un valor distinto de cero?¿Evité dejar variables en cero sin inicializar entre declaración y uso?
Reglas relacionadas¶
0x7001h: Siempre debés inicializar las variables a un valor conocido — toda variable se inicializa a un valor conocido.
0x3016h: Orden incorrecto o sospechoso de argumentos en llamadas a memset — orden canónico de los argumentos de
memset.0x300Bh: Usá siempre sizeof en las asignaciones de memoria dinámica, prefiriendo sizeof(*ptr) — tamaño correcto en la inicialización de memoria dinámica.
0x301Ch: Detector de variables locales no inicializadas con modificador const — las variables
constse inicializan en la declaración.