Regla 0x2011h: Prohibición de asignaciones múltiples a una variable sin lectura intermedia (dead store)
Funciones, contratos y modularizacion (0x20XX)
0x2011h: Prohibición de asignaciones múltiples a una variable sin lectura intermedia (dead store)¶
Enunciado normativo¶
NO DEBE asignarse un valor a una variable que jamás será leída antes de la siguiente escritura. Toda asignación DEBE tener un consumidor posterior, o eliminarse.
¿Por qué existe esta regla?¶
El problema¶
Un dead store es una escritura que se descarta sin que nadie la lea. El valor anterior se pierde y el código aparenta hacer un trabajo que no influye en el resultado. A veces es un residuo de una versión anterior del algoritmo; a veces es un bug, porque el programador creía que ese valor se usaba en algún lado.
El compilador detecta las más simples con -Wunused-but-set-variable, pero las
sutiles —una variable que se escribe, se lee en una rama y se vuelve a escribir
sin que la lectura importe— requieren razonamiento. Igual que con
0x2006h: Mantené el alcance de las variables al mínimo posible, la presencia de una variable con varias escrituras y pocas
lecturas es un disparador de revisión.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Bug silencioso | El valor que el autor creía vigente fue pisado por una escritura previa. |
| Código residual | Queda una línea muerta que nadie se anima a borrar. |
| Ruido | El lector debe resolver qué escritura vale, para descubrir que algunas no importan. |
| Optimización | El compilador elimina el cálculo, sorprendiendo a quien depura. |
Fundamento en el estándar y en la cátedra¶
Un dead store no es comportamiento indefinido: el programa es correcto pero confuso. C11 solo define el efecto de cada sentencia de expresión (§6.8.3); no prohíbe pisar un valor. La cátedra lo prohíbe porque atenta contra la claridad (0x0001h: La claridad y prolijidad son de máxima importancia) y porque suele delatar errores de lógica. La contracara es 0x7001h: Siempre debés inicializar las variables a un valor conocido: si una variable debe evolucionar, cada paso debe leerse o justificarse.
Alcance y excepciones¶
Cubre variables locales y parámetros copiados. No marca como dead store:
Asignaciones dentro de un lazo cuyo valor se lee en la iteración siguiente.
Inicializaciones a un valor centinela que serán sobrescritas, si el centinela evita un warning de “possibly uninitialized” y el flujo lo justifica.
Asignaciones en ramas mutuamente excluyentes.
La clave es si existe una lectura en algún camino que use el valor antes de la próxima escritura.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Escritura descartada¶
int valor = 10;
valor = 20;
printf("%d\n", valor);Por qué falla: el 10 nunca se lee; la primera inicialización es inútil. La
línea sobra y, peor, distrae al lector sobre cuál es el valor inicial real.
❌ Contraejemplo 2 — Cálculo cuyo resultado nunca se usa¶
int calcular(int n)
{
int total = 0;
int descartado = n * 2;
for (int i = 0; i < n; i++) {
total += i;
}
return total;
}Por qué falla: descartado se calcula y nunca se lee. Es código residual que
el compilador elimina, y que sugiere una intención que quedó a mitad de camino.
✅ Ejemplo conforme 1 — Inicialización única y uso posterior¶
int valor = 20;
printf("%d\n", valor);La variable nace con el valor definitivo y se lee. No hay escritura perdida ni estado intermedio que rastrear.
✅ Ejemplo conforme 2 — Evolución leída en cada paso¶
int suma_acumulada(const int v[], size_t n)
{
int suma = 0;
for (size_t i = 0; i < n; i++) {
suma += v[i];
}
return suma;
}Cada actualización de suma se lee en la siguiente iteración (suma += es a la
vez lectura y escritura). No hay paso perdido; el acumulador tiene un propósito
claro.
⚠️ Casos límite¶
Aritmética con signo: un dead store puede esconder una operación con overflow, que sí es UB. El caso de 0x2018h: Los comparadores no deben usar resta sujeta a desbordamiento muestra cómo una expresión compacta puede ser incorrecta aunque el resultado se use.
Variables de depuración: si una variable solo existe para inspección con
gdb, se elimina antes de la entrega (0x0202h: No dejes código comentado (dead code) en los archivos fuente).-O2: el optimizador puede borrar el cálculo y confundir al depurador; es otra razón para no dejar código muerto.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gcc / clang | gcc -Wall -Wextra -Wunused-but-set-variable -std=c11 ... | “variable set but not used”. |
cppcheck | cppcheck --enable=warning archivo.c | “Variable is assigned a value that is never used”. |
gaff | gaff check archivo.c | Escrituras consecutivas sin lectura intermedia. |
Checklist de autocontrol¶
¿Cada variable que asigné se lee antes de la próxima escritura?
¿Quedó alguna inicialización que nadie consume?
¿Hay cálculos cuyo resultado nunca se usa?
¿Puedo borrar líneas sin cambiar el resultado? Si sí, sobran.
Reglas relacionadas¶
0x7001h: Siempre debés inicializar las variables a un valor conocido — inicializar a un valor conocido; no a un valor descartado.
0x2006h: Mantené el alcance de las variables al mínimo posible — el alcance mínimo reduce las variables longevas y los dead stores.
0x0202h: No dejes código comentado (dead code) en los archivos fuente — el código residual se elimina, no se comenta.