La ejecución de un programa sigue, por defecto, una línea recta: una instrucción tras otra. Sin embargo, para que un programa sea inteligente, necesita tomar decisiones. La sentencia if es la estructura que permite desviar el flujo de ejecución basándose en si una expresión es verdadera o falsa.
A nivel de arquitectura, la CPU no entiende de “conceptos lógicos”, sino de comparaciones de números. Cuando escribes un if, el compilador traduce esa lógica en instrucciones de comparación y saltos condicionales (jump). Si la condición se cumple, el procesador salta a una dirección de memoria específica; si no, continúa su camino normal. Esta capacidad de bifurcar el camino es lo que permite que un software reaccione a diferentes entradas.
En C, el concepto de “verdadero” y “falso” es muy simple: el número 0 es falso y cualquier otro número distinto de cero es verdadero. Para facilitar la lectura, el estándar [disponible desde C99] introdujo stdbool.h, que nos permite usar el tipo bool y los valores true y false. Antes de C99, todo era una cuestión de enteros, lo que hacía que el código fuera más críptico.
Deberás usar estas estructuras siempre que la lógica de tu programa dependa de una condición: validar si un puntero es válido, verificar si un valor está dentro de un rango permitido o comprobar si un sensor ha enviado una señal de error. Si usas mal estas estructuras, puedes incurrir en el problema del “dangling else” (un else que se asocia al if incorrecto debido a la falta de llaves) o, más peligrosamente, en errores de lógica donde una asignación accidental se evalúa como una comparación, haciendo que un bloque de código se ejecute siempre sin que el compilador te avise.
#include <stdio.h>
#include <stdbool.h> // [disponible desde C99]
int main(void) {
int temperatura = 25;
bool alarma_activa = true;
int *lectura_sensor = &temperatura;
// 1. Estructura if / else if / else
// Las llaves {} son vitales para delimitar el bloque de código
if (temperatura > 30) {
printf("Alerta: Temperatura crítica alta.\n");
} else if (temperatura < 0) {
printf("Alerta: Temperatura de congelación.\n");
} else {
printf("Estado: Temperatura en rango normal.\n");
}
// 2. Uso de tipos booleanos [C99]
// No hace falta comparar (alarma_activa == true); basta con la variable
if (alarma_activa) {
printf("Atención: El sistema de alarma está encendido.\n");
}
// 3. Comprobación de punteros
// 'if (ptr)' es idiomático y equivale a 'if (ptr != NULL)'
if (lectura_sensor) {
printf("Valor actual del sensor: %d\n", *lectura_sensor);
}
return 0;
}
En el código anterior, la primera estructura utiliza un bloque if / else if / else para evaluar la variable temperatura. El programa evalúa la primera condición; si temperatura > 30 es falsa, salta a la siguiente (else if). Si ninguna se cumple, el bloque else actúa como la opción por defecto. Fíjate en el uso de las llaves {}; aunque para una sola línea no son estrictamente necesarias, en producción se usan siempre para evitar errores de lectura y problemas de indentación.
En la segunda parte, evaluamos alarma_activa. Como alarma_activa ya es de tipo bool, pasarla directamente al if es la forma más limpia y eficiente de trabajar. Finalmente, al evaluar lectura_sensor, estamos comprobando si el puntero apunta a una dirección de memoria válida. En C, un puntero que es NULL se evalúa como 0 (falso), por lo que if (lectura_sensor) es una forma abreviada y muy común de asegurar que no intentaremos acceder a una dirección nula, lo cual causaría un error de segmentación.
El error frecuente
Un error clásico que los compiladores a veces permiten pasar como válidos, pero que arruina la lógica, es el uso del operador de asignación = en lugar del operador de igualdad ==.
int x = 5;
// ERROR: Esto no comprueba si x es 10.
// Asigna 10 a x, la expresión devuelve 10 (que no es cero),
// por lo tanto, el bloque siempre se ejecuta.
if (x = 10) {
printf("Esto se ejecutará siempre, aunque x valiera 5 antes.\n");
}
Este error es particularmente difícil de detectar a simple vista en archivos de cientos de líneas. Herramientas como AddressSanitizer o el análisis estático de gcc -Wextra pueden ayudarte a identificar comparaciones que parecen asignaciones, pero la mejor práctica es usar siempre las llaves {} y tratar de mantener las condiciones lo más simples posible.
Los compiladores modernos optimizan estas ramas mediante instrucciones de selección condicional, evitando saltos costosos para la tubería (pipeline) de la CPU cuando la lógica es predecible.
N° 31