Regla 0x1013h: Detector de expresiones booleanas complejas sin paréntesis aclaratorios
Estructuras de control y flujo (0x10XX)
0x1013h: Detector de expresiones booleanas complejas sin paréntesis aclaratorios¶
Enunciado normativo¶
Cuando en una misma expresión se combinen los operadores lógicos
&&y||, DEBEN usarse paréntesis que expliciten la agrupación deseada, aun cuando la precedencia del lenguaje ya la determine. NO DEBE dejarse la agrupación librada a la precedencia implícita.
¿Por qué existe esta regla?¶
El problema¶
El lenguaje resuelve la precedencia: && liga más fuerte que ||, por lo que
a && b || c significa (a && b) || c. El compilador no tiene dudas, pero el
lector sí: la mayoría de las personas no recuerda la tabla de precedencia y
debe detenerse a pensarla.
Los paréntesis son documentación ejecutable. Cuentan la agrupación que el autor quiso, no la que el estándar impone. Si coinciden, no cambian la semántica; si no coinciden, revelan un bug de intención antes de que llegue a producción.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Bug de intención | La agrupación real no es la que el autor imaginaba. |
| Legibilidad | El lector debe aplicar la tabla de precedencia para entender. |
| Mantenibilidad | Agregar un término cambia la agrupación de forma invisible. |
| Revisión | El corrector no puede distinguir intención de accidente. |
Fundamento en el estándar y en la cátedra¶
ISO/IEC 9899:2011 §6.5.13 y §6.5.14 fijan la precedencia de && sobre ||.
La cátedra no discute esa precedencia: exige hacerla explícita. Se relaciona con
0x1004h: Las condiciones complejas deben simplificarse o comentarse (extraer subcondiciones nombradas) y con 0x1010h: Prohibición de comparaciones encadenadas no idiomáticas en C (a < b < c)
(comparaciones encadenadas). Los paréntesis también evitan que un & o |
mal tecleado pase inadvertido.
Alcance y excepciones¶
Aplica cuando coexisten
&&y||en una misma expresión.Una cadena de un solo tipo (
a && b && c,x || y || z) no exige paréntesis, aunque pueden usarse por claridad.Los paréntesis alrededor de una comparación simple (
(x > 0) && ...) son opcionales; la regla apunta a la mezcla de operadores lógicos.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — mezcla sin paréntesis¶
if (a && b || c)
{
actuar();
}Por qué falla: aunque el compilador agrupa (a && b) || c, el lector debe
recordarlo. Si el autor pretendía a && (b || c), el código es incorrecto y no
hay ninguna pista.
❌ Contraejemplo 2 — bit a bit en lugar de lógico¶
if (a > 0 & b > 0)
{
actuar();
}Por qué falla: & evalúa ambos lados siempre, sin cortocircuito. Si el segundo
operando desreferencia un puntero, puede provocar un fallo que && habría
evitado.
✅ Ejemplo conforme 1 — paréntesis explícitos¶
if ((a && b) || c)
{
actuar();
}La agrupación deseada queda escrita; el lector no necesita la tabla de precedencia.
✅ Ejemplo conforme 2 — cortocircuito con operadores lógicos¶
if (ptr != NULL && (ptr->activo || ptr->es_admin))
{
actuar();
}Los paréntesis aclaran que la alternancia interna se combina con la validez del
puntero, y && garantiza que ptr->activo no se evalúe si ptr es NULL.
⚠️ Casos límite¶
Precedencia igual:
a && b && cno necesita paréntesis, pero puede llevar((a && b) && c)si el autor quiere explicitar el orden.Negaciones:
!a && bes claro;!(a && b)es otra cosa y los paréntesis ya la distinguen.Reordenamiento: al agregar paréntesis no se debe alterar el cortocircuito ni los efectos secundarios de cada lado.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Regla 0x1013h: mezcla de &&/` |
gcc | gcc -Wall -Wextra -Wparentheses | Suele sugerir paréntesis al mezclar operadores lógicos. |
| Revisión manual | — | && y ` |
Checklist de autocontrol¶
¿La expresión combina
&&y||?¿Agregué paréntesis que muestren la agrupación pretendida?
¿Usé
&&/||y no&/|en la condición?¿El cortocircuito evita desreferencias o llamadas innecesarias?
Reglas relacionadas¶
0x1004h: Las condiciones complejas deben simplificarse o comentarse — extraer subcondiciones complejas a variables con nombre.
0x1010h: Prohibición de comparaciones encadenadas no idiomáticas en C (a < b < c) — las comparaciones encadenadas se separan con
&&.0x1005h: Reemplazá las condiciones ambiguas basadas en la ‘veracidad’ (truthiness) del tipo de dato — comparaciones explícitas según el tipo del dato.
Antipatrón: Expresión booleana tautológica o contradictoria¶
Síntoma en el código del estudiante¶
La condición combina una variable consigo misma de forma que el resultado es siempre verdadero o siempre falso:
if (x && !x)
{
marcar_error();
}
if (activo || !activo)
{
registrar();
}Diagnóstico¶
Mecanismo del defecto¶
Por álgebra booleana, x && !x es siempre falso y x || !x es siempre
verdadero, para cualquier valor de x. El compilador puede doblar la rama
muerta, pero la intención del autor queda sepultada. Estos patrones suelen
aparecer cuando se agregan términos sin revisar la condición completa.
Consecuencia observable¶
La rama inalcanzable (x && !x) nunca se ejecuta y el autor cree que sí; la
rama tautológica (x || !x) se ejecuta siempre. En cualquiera de los dos casos
el programa “hace algo” y no hay error, lo que convierte al defecto en un bug
silencioso de lógica.
Fundamento en el estándar C11¶
ISO/IEC 9899:2011 §6.8.4.1 define la evaluación de condiciones y §6.5.13–6.5.14
la semántica de && y ||. El estándar no considera estas expresiones
inválidas; son lógicamente degeneradas. La cátedra las trata como señal de
error de razonamiento y pide simplificarlas o eliminarlas.
Corrección idiomática¶
❌ Código con el antipatrón¶
if (x && !x)
{
marcar_error();
}
if (activo || !activo)
{
registrar();
}✅ Código refactorizado¶
/* la primera rama es inalcanzable: se elimina */
if (activo)
{
registrar();
}Si la intención era detectar algo imposible, la condición debe reescribirse. Si era una tautología, sobra la condición y queda la sentencia directa.
Errores típicos al compilar o ejecutar¶
Con -Wall y optimización:
warning: logical 'and' of mutually exclusive expressions is always false
warning: logical 'or' of collectively exhaustive expressions is always true
La rama marcar_error() nunca se ejecuta; la rama registrar() se ejecuta
siempre, con independencia de activo.Checklist de verificación¶
¿Alguna condición usa la misma variable dos veces con signos opuestos?
¿El resultado de la expresión depende realmente de algún dato?
¿La rama que escribí es alcanzable?
¿Simplifiqué la expresión o la dejé con un comentario?
Reglas relacionadas¶
0x1004h: Las condiciones complejas deben simplificarse o comentarse — simplificar condiciones complejas con variables auxiliares.
0x1013h: Detector de expresiones booleanas complejas sin paréntesis aclaratorios — paréntesis al mezclar
&&y||.0x100Ch: Espaciado obligatorio alrededor de operadores ternarios (‘? :’) — regla asociada a este antipatrón.
Antipatrón: Uso de operador bit a bit (&, |) en condición lógica en lugar de booleano (&&, ||)¶
Síntoma en el código del estudiante¶
La condición usa & o | (bit a bit) donde se pretendía la lógica booleana
&& o ||:
if (a > 0 & b > 0)
{
actuar();
}Diagnóstico¶
Mecanismo del defecto¶
& y | operan bit a bit y evalúan siempre ambos operandos; && y ||
evalúan el segundo solo si hace falta (cortocircuito, ISO/IEC 9899:2011
§6.5.13–6.5.14). Sobre resultados de comparación (0 o 1) el resultado aritmético
coincide muchas veces, lo que esconde el problema. La diferencia se manifiesta
cuando el operando derecho tiene efectos secundarios o es inválido.
Consecuencia observable¶
Con &, una expresión como ptr != NULL & ptr->campo > 0 evalúa ptr->campo
aunque ptr sea NULL, provocando un fallo de segmentación que && habría
evitado. Además, si los lados no son 0/1 sino máscaras, el resultado difiere por
completo del esperado.
Fundamento en el estándar C11¶
ISO/IEC 9899:2011 §6.5.10–6.5.12 definen &, ^ y | como operadores bit a
bit, y §6.5.13–6.5.14 los lógicos con cortocircuito. Usar los primeros como
condición booleana no es un error de sintaxis, pero viola la intención y la
seguridad del cortocircuito. La cátedra exige los operadores lógicos.
Corrección idiomática¶
❌ Código con el antipatrón¶
if (a > 0 & b > 0)
{
actuar();
}
if (ptr != NULL & ptr->activo)
{
registrar(ptr);
}✅ Código refactorizado¶
if (a > 0 && b > 0)
{
actuar();
}
if (ptr != NULL && ptr->activo)
{
registrar(ptr);
}Con && el segundo operando solo se evalúa si el primero es verdadero: la
desreferencia de ptr queda protegida por la comprobación de NULL.
Errores típicos al compilar o ejecutar¶
Con -Wall:
warning: suggest parentheses around comparison in operand of '&'
[-Wparentheses]
En ejecución, la versión con '&' y ptr == NULL:
Segmentation fault (core dumped)
- o con -fsanitize=address:
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000Checklist de verificación¶
¿La condición usa
&o|en lugar de&&o||?¿El operando derecho puede ser inseguro si no hay cortocircuito?
¿Los operandos son comparaciones (0/1) o máscaras de bits?
¿Compilé con
-Wparenthesespara detectar el uso mixto?
Reglas relacionadas¶
0x1013h: Detector de expresiones booleanas complejas sin paréntesis aclaratorios — norma este antipatrón: paréntesis y operadores lógicos correctos.
0x1005h: Reemplazá las condiciones ambiguas basadas en la ‘veracidad’ (truthiness) del tipo de dato — comparaciones explícitas y chequeo de punteros.
0x300Ch: Verificá siempre los límites de los arreglos antes de acceder a sus elementos — validar límites antes de acceder a memoria.