wandres.dev
COMPETENTE · Operadores y expresiones

Operadores, expresiones y conversiones

Precedencia, promociones enteras y las conversiones implícitas que causan bugs sutiles. Dominar cómo C evalúa expresiones evita una clase entera de errores.

⏱ 14 min

Las expresiones parecen lo trivial de un lenguaje, pero en C esconden reglas que sorprenden: promociones automáticas, conversiones silenciosas y una precedencia con trampas. Entenderlas es la diferencia entre código que hace lo que crees y bugs que solo aparecen con ciertos valores.

🎯 Al terminar esta lección sabrás
  • Operadores y su precedencia (y las trampas clásicas).
  • Las promociones enteras.
  • Las conversiones aritméticas usuales.
  • Operadores a nivel de bits.

Operadores esenciales

int a = 7, b = 2;
a / b        // 3  — división ENTERA (trunca)
a % b        // 1  — módulo (resto)
a & b        // 2  — AND de bits
a | b        // 7  — OR de bits
a ^ b        // 5  — XOR de bits
a << 1       // 14 — desplazamiento a la izquierda (×2)
a >> 1       // 3  — desplazamiento a la derecha (÷2)
⚠️
7 / 2 no es 3.5

La trampa número uno: / entre dos enteros es división entera, trunca hacia cero. 7 / 2 es 3, no 3.5. Para obtener decimales, al menos un operando debe ser de coma flotante: 7.0 / 2 da 3.5. Muchísimos bugs de cálculo vienen de olvidar esto.

Precedencia: usa paréntesis

C tiene una tabla de precedencia larga y no siempre intuitiva. En vez de memorizarla entera, aplica una regla: cuando dudes, pon paréntesis.

// clásico: & tiene MENOS precedencia que ==
if (flags & MASK == 0)     // ❌ se evalúa como flags & (MASK == 0)
if ((flags & MASK) == 0)   // ✅ lo que querías
💡
Los paréntesis son gratis; los bugs no

Nadie te va a felicitar por memorizar que && tiene más precedencia que || pero menos que ==. Los paréntesis explícitos hacen el código correcto y legible. El compilador con -Wall incluso te avisa de las combinaciones más peligrosas (-Wparentheses). No es señal de debilidad usarlos: es señal de que valoras tu tiempo.

Promociones enteras

Antes de operar, C promociona los tipos enteros pequeños (char, short) a int. Esto explica comportamientos raros:

char a = 100, b = 100;
char c = a + b;      // a y b se promocionan a int: 200, que cabe en int
                     // pero al asignar a char (a menudo 8 bits con signo)
                     // 200 no cabe → resultado dependiente de implementación

La aritmética “ocurre en int” aunque los operandos sean más pequeños. Tenerlo presente evita sorpresas al mezclar tipos.

Las conversiones aritméticas usuales

Cuando operas tipos distintos, C convierte ambos a un tipo común siguiendo reglas fijas (el “de mayor rango gana”). El caso peligroso es mezclar con y sin signo:

unsigned u = 0;
int s = -1;
u - 1        // unsigned: da un número ENORME, no -1
s < u        // -1 se convierte a unsigned gigante → false (¡trampa!)
Las conversiones implícitas son el terror silencioso de C

Aquí vive una categoría entera de bugs que no crashean —solo dan el resultado equivocado con ciertos valores—. C convierte tipos por ti, sin avisar, siguiendo reglas que casi nadie recuerda de memoria. La defensa profesional es triple: (1) compila con -Wconversion -Wsign-compare para que el compilador te señale las conversiones sospechosas; (2) evita mezclar con y sin signo en la misma expresión; (3) cuando la conversión sea intencional, hazla explícita con un cast, para que quede claro que sabes lo que haces. El código que respeta esto no tiene esa clase de bugs fantasma.

Operadores a nivel de bits

En sistemas manipularás bits constantemente (flags, máscaras, registros de hardware):

#define LEER   (1u << 0)   // 0b001
#define ESCRIBIR (1u << 1) // 0b010
#define EJECUTAR (1u << 2) // 0b100

unsigned permisos = LEER | ESCRIBIR;   // activar dos flags
permisos |=  EJECUTAR;                  // activar uno
permisos &= ~ESCRIBIR;                  // desactivar uno
bool puede_leer = permisos & LEER;      // comprobar

Este patrón —máscaras con |, &, ~, <<— es el pan de cada día del programador de sistemas. Usa siempre literales unsigned (1u) para evitar problemas de signo en los desplazamientos.

⚔️ Domina las expresiones
  1. Comprueba que 7 / 2 es 3 y 7.0 / 2 es 3.5.
  2. Reproduce el bug de precedencia flags & MASK == 0 y arréglalo con paréntesis.
  3. Provoca la trampa de signo s < u y observa el aviso de -Wsign-compare.
  4. Escribe un conjunto de flags con máscaras de bits: activa, desactiva y comprueba.