Control de flujo
if, switch, bucles, break/continue, y el uso disciplinado de goto. Cómo estructurar la lógica de forma clara, con las mejoras y atributos de C23.
El control de flujo decide qué hace tu programa y en qué orden. C te da las herramientas clásicas más un par que se malinterpretan (switch con su fallthrough, goto con su mala fama). Usarlas con criterio hace la lógica legible; usarlas mal, un laberinto.
- if/else y el operador ternario.
- switch, el fallthrough y
[[fallthrough]]. - Bucles: for, while, do-while.
- break, continue y el goto disciplinado.
Condicionales
if (x > 0) {
puts("positivo");
} else if (x < 0) {
puts("negativo");
} else {
puts("cero");
}
// ternario: una expresión, no una sentencia
int abs = (x < 0) ? -x : x;
Omitir las llaves en un if de una línea es una fuente célebre de bugs (recuerda el “goto fail” de Apple, un if sin llaves que rompió la seguridad de TLS). Pon llaves siempre: el coste es un par de caracteres, el beneficio es que añadir una segunda línea nunca romperá la lógica en silencio.
switch y el fallthrough
switch compara contra valores constantes. Su peculiaridad: sin break, la ejecución cae al siguiente caso (fallthrough). A veces lo quieres; casi siempre es un bug:
switch (comando) {
case 'a':
hacer_a();
break; // sin esto, caería a 'b'
case 'b':
case 'c': // fallthrough intencional: b y c hacen lo mismo
hacer_bc();
break;
case 'd':
preparar();
[[fallthrough]]; // C23: dice "el fallthrough es a propósito"
case 'e':
hacer_e();
break;
default:
error();
}
El fallthrough silencioso es tan peligroso que los compiladores avisan de él (-Wimplicit-fallthrough). El atributo [[fallthrough]] de C23 te deja decir explícitamente “sí, quiero que caiga aquí”, silenciando el aviso solo donde es intencional. Así el compilador sigue cazando los fallthrough accidentales (bugs) mientras respeta los deliberados. Es un ejemplo perfecto de la filosofía de C23: hacer la intención explícita para que la herramienta pueda ayudarte.
Bucles
// for: cuando conoces el patrón de iteración
for (int i = 0; i < n; i++) { /* … */ }
// while: cuando iteras hasta una condición
while (hay_datos()) { procesar(); }
// do-while: cuando el cuerpo debe correr al menos una vez
do { intentar(); } while (fallo && quedan_intentos--);
Declara la variable del bucle dentro del for (int i = 0): limita su scope y evita fugas de variables entre bucles.
break, continue y goto
break sale del bucle o switch; continue salta a la siguiente iteración. Y luego está goto, el operador con peor reputación de C:
// el patrón legítimo de goto: limpieza al salir de una función
int f(void) {
FILE *a = fopen("x", "r");
if (!a) return -1;
char *buf = malloc(1024);
if (!buf) goto cerrar_a; // salta a la limpieza
// … trabajo …
free(buf);
cerrar_a:
fclose(a);
return 0;
}
goto merece su mala fama cuando salta caóticamente (el “spaghetti code”). Pero tiene un uso idiomático y respetado en C, sobre todo en el kernel de Linux: saltar hacia adelante a una etiqueta de limpieza cuando algo falla a mitad de una función con varios recursos. Como C no tiene destructores ni defer, este patrón evita anidar diez if o duplicar el código de liberación. La regla: goto solo hacia adelante, solo para manejo de errores y limpieza. Cualquier otro uso, replantéalo.
- Escribe un
switchcon un fallthrough intencional marcado con[[fallthrough]]. - Provoca un fallthrough accidental (olvida un
break) y observa el aviso del compilador. - Convierte un bucle
whileendo-whiley razona cuándo importa la diferencia. - Escribe una función con dos recursos y usa
gotopara la limpieza ordenada al fallar.