Bucles en C: Estructuras de repetición y su uso correcto

Los bucles son estructuras de control que permiten repetir un bloque de código múltiples veces. En su nivel más básico, el compilador traduce estas estructuras en instrucciones de salto (jump) y comparaciones a nivel de CPU. Usamos un while cuando la condición debe evaluarse antes de entrar al bucle (pudiendo no ejecutarse nunca); un do-while cuando necesitamos garantizar que el cuerpo se ejecute al menos una vez; y un for cuando tenemos un número definido de iteraciones o un índice que debemos incrementar. Si no definimos correctamente la condición de salida, podemos caer en un bucle infinito que bloquee la ejecución, o incurrir en errores de acceso a memoria si el índice se descontrola.

Cuando recorras un array, fíjate siempre en el tipo de dato de tu índice. En C, lo correcto es usar size_t [disponible desde C89] en lugar de int. size_t es un tipo de entero sin signo que garantiza ser lo suficientemente grande como para representar el tamaño máximo de cualquier objeto en memoria, lo que evita errores de desbordamiento en sistemas con memoria muy extensa.

#include <stdio.h>
#include <stdbool.h>

int main(void) {
    // Array de lecturas de un sensor
    float lecturas[] = {22.5, 23.1, 19.8, 25.4, 24.0};
    // size_t es el estándar para índices y tamaños
    size_t n = sizeof(lecturas) / sizeof(lecturas[0]);

    // 1. Bucle for: ideal para iterar sobre estructuras de tamaño conocido.
    // En C99, podemos declarar 'i' dentro del for, limitando su ámbito.
    printf("--- Registro de lecturas ---\n");
    for (size_t i = 0; i < n; i++) {
        if (lecturas[i] < 20.0f) {
            // 'continue' salta el resto del cuerpo y va a la siguiente iteración
            printf("Advertencia: Lectura baja detectada (%.1f)\n", lecturas[i]);
            continue;
        }
        printf("Lectura %zu: %.1f\n", i, lecturas[i]);
    }

    // 2. Bucle while: la condición se evalúa al principio.
    printf("\n--- Sistema de reinicio ---\n");
    int intentos = 3;
    while (intentos > 0) {
        printf("Intentando reconectar... (Intentos restantes: %d)\n", intentos);
        intentos--;
        if (intentos == 0) {
            printf("Error crítico: No se pudo reconectar.\n");
            break; // 'break' sale inmediatamente del bucle actual
        }
    }

    // 3. Bucle do-while: la condición se evalúa al final.
    // El cuerpo se ejecuta al menos una vez, ideal para menús de usuario.
    int opcion;
    do {
        printf("\nMenú de control:\n1. Ver estado\n0. Salir\nSeleccione: ");
        // Nota: En un programa real, se debe validar el retorno de scanf
        if (scanf("%d", &opcion) != 1) break; 
    } while (opcion != 0);

    // 4. Bucle infinito: uso idiomático para procesos de larga duración.
    printf("\n--- Servicio de monitoreo activo (simulado) ---\n");
    // for(;;) es un estándar en sistemas embebidos para bucles infinitos
    for (;;) {
        printf("Monitoreando... (Presiona Ctrl+C para detener)\n");
        // Para este ejemplo, simulamos una salida para no bloquear el terminal
        break; 
    }

    printf("Sistema apagado correctamente.\n");
    return 0;
}

Desglose del código

En el primer bloque, el bucle for utiliza size_t i. Al usar i < n, nos aseguramos de que el índice nunca sea igual a la longitud del array, evitando el error clásico de “fuera de rango”. La declaración for (size_t i = 0; ...) es una característica de [C99] que mejora la limpieza del código, ya que la variable i deja de existir una vez que el bucle termina, evitando contaminar el resto de la función con nombres de variables genéricos.

En el while, observamos cómo la variable intentos controla el flujo. Si la condición intentos > 0 fuera falsa desde el inicio, el cuerpo nunca se ejecutaría. La instrucción break dentro del if es un mecanismo de escape que interrumpe el flujo y salta directamente a la siguiente línea de código después del bloque while.

El bucle do-while es fundamental para la interacción. Fíjate que incluso si el usuario ingresa un 0 inmediatamente, la pregunta del menú se mostrará al menos una vez. Esto es una decisión de diseño: la lógica de “pedir y luego comprobar” es la base de la mayoría de las interfaces de línea de comandos.

Finalmente, el for (;;) es una forma idiomática de escribir un bucle que no tiene una condición de parada intrínseca. Aunque while (1) es técnicamente equivalente, los programadores de sistemas suelen preferir for (;;) porque visualmente deja claro que es un bucle de control de flujo infinito diseñado para ser gestionado mediante break o señales externas.

El error frecuente

Un error común ocurre al intentar salir de bucles anidados. Si tienes un bucle dentro de otro y usas break para intentar salir de ambos, solo saldrás del más interno.

// CÓDIGO CON ERROR LÓGICO
for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        if (condicion_de_salida_critica) {
            break; // ¡Esto solo rompe el bucle de 'j'!
        }
    }
    // El programa sigue aquí cuando j termina, pero i continúa su ciclo.
}

Si necesitas salir de múltiples niveles de anidamiento, tienes tres opciones profesionales:
1. Usar una bandera (una variable bool tipo exit_flag) que se compruebe en cada nivel de los bucles.
2. Refactorizar el bucle anidado en una función propia y usar return.
3. Utilizar goto para saltar directamente fuera de la estructura de bucles. Aunque el goto suele ser criticado, en el caso específico de salir de bucles anidados en C, es una solución eficiente y aceptada por la mayoría de las guías de estilo de kernels y sistemas de bajo nivel, ya que evita el ruido de múltiples comprobaciones de banderas.

AddressSanitizer (ASan) es excelente para detectar si un error en el control de un bucle (como un i <= n en lugar de i < n) te está llevando a leer memoria prohibida, lo cual es un comportamiento indefinido que puede ser muy difícil de depurar con un printf.

33

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio