Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Técnicas de Simulación de Estructuras de Control

Alternativas y patrones para simular comportamientos de lazos

Universidad Nacional de Río Negro

Introducción

En ciertas situaciones de programación, podés encontrarte con la necesidad de simular comportamientos de estructuras de control que no están disponibles en el lenguaje que estás usando, o que querés evitar por razones de estilo o compatibilidad. Este apunte explora técnicas para simular diversas estructuras de control usando construcciones más básicas, con especial énfasis en la simulación de do...while usando while y otras alternativas.

La comprensión de estas técnicas no solo es útil para casos específicos donde las estructuras originales no están disponibles, sino que también proporciona una comprensión más profunda de cómo funcionan internamente los lazos y las estructuras de control.

Reglas de estilo que resuelve

Esta refactorización no es un ejercicio abstracto de estilo: cada técnica que sigue existe para resolver, en el código concreto, los problemas que modelan las reglas de la cátedra. Cuando reemplazás un goto por una función de limpieza, cuando cambiás un while de conteo por un for o cuando preferís una bandera de control en lugar de un continue, estás aplicando exactamente las reglas de la tabla. Por eso conviene leer el apunte como un catálogo de refactorizaciones guiadas por reglas: cada patrón que sigue tiene una regla detrás que explica por qué conviene y qué bug evita.

ReglaProblema que modelaCómo lo resuelve esta refactorización
0x1001h: Todas las estructuras de control deben utilizar llavesCuerpos de control sin llaves que aceptan en silencio una sentencia de más fuera del bloque.Los patrones estructurados delimitan cada if, for, while y do-while con llaves, de modo que el cuerpo es un bloque explícito y no una única sentencia gobernada por posición.
0x1002h: Restringí el uso de break y continue; preferí lazos con bandera de controlbreak y continue como único mecanismo de corte: la condición del lazo deja de reflejar la causa real de salida.El Método 2 traslada el motivo de salida a una bandera booleana en la condición del while, y los ejemplos evitan continue reescribiendo la lógica con if/else.
0x1003h: Utilizá el lazo for para iteraciones con rango o contador definido y while para lazos controlados por condiciones lógicaswhile usados para contar iteraciones, con el incremento escondido en el cuerpo y riesgo de lazo infinito.La conversión de for a while y los ejemplos de claridad muestran cuándo corresponde cada lazo: for para rango o contador, while para condición lógica.
0x1006h: No utilizar la instrucción gotogoto que convierte el flujo en un grafo: para seguir el programa hay que rastrear todas las etiquetas.Las técnicas de la sección de goto reemplazan el salto por funciones de limpieza, banderas de estado y retornos tempranos dentro de estructuras estándar.
0x1011h: Prohibición de saltos no estructurados goto fuera del patrón canónico de liberación de recursosgoto hacia atrás, saltos a etiquetas intermedias y dobles liberaciones de recursos.El patrón canónico de limpieza concentra la liberación en un único bloque terminal, y la versión estructurada reemplaza el salto por una función limpiar_recursos invocada en cada ruta de error.
0x100Eh: Delimitación obligatoria con bloque de llaves en lazos do-whiledo ... while sin llaves, donde el while final parece un lazo aparte y el cuerpo pierde su delimitación.La verificación de equivalencia usa el do ... while nativo con llaves explícitas, y todas las simulaciones delimitan el cuerpo con bloque.
0x0013h: Detector de etiquetas de salto goto no alineadas al margen izquierdoEtiquetas de salto indentadas que se confunden con sentencias o llamadas dentro del cuerpo.Los ejemplos comentados de goto muestran la etiqueta de limpieza en la columna 1, alineada al margen izquierdo como destino de salto reconocible.

Simulación de do...while con while

Método 1: lazo Infinito con break

La técnica más directa para simular un do...while es usar un lazo while(1) con una condición de salida explícita. Ojo: el break que aparece acá es una salida anticipada legítima, pero como mecanismo único de corte está restringido por 0x1002h: Restringí el uso de break y continue; preferí lazos con bandera de control, así que reservalo para el caso en que la condición de salida sea realmente lateral:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Comportamiento deseado (do...while):
// do {
//     // código del lazo
// } while (condicion);
// Simulación equivalente:
while (1)
{
    // código del lazo
    if (!condicion)
    {
        break;
    }
}

Simulación básica de do...while

Ejemplo Práctico: Validación de Entrada

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdbool.h>
#include <stdio.h>
int main()
{
    int clave;
    bool clave_valida = false;
    printf("=== Sistema de Autenticación ===\n");
    while (1)
    {
        printf("Ingresá la clave (123): ");
        scanf("%d", &clave);
        if (clave == 123)
        {
            clave_valida = true;
            break;
        }
        else
        {
            printf("Clave incorrecta. Intentá nuevamente.\n");
        }
    }
    printf("Acceso concedido.\n");
    return 0;
}

Validación de entrada con simulación de do...while

Método 2: Variable de Control Booleana

Una alternativa más explícita es usar una variable booleana para controlar la continuación del lazo. Esta es la forma preferida por 0x1002h: Restringí el uso de break y continue; preferí lazos con bandera de control: el motivo de salida viaja en la condición y el lector no necesita recordar una puerta lateral dentro del cuerpo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdbool.h>
int validar_entrada()
{
    bool continuar = true;
    int numero;
    while (continuar)
    {
        printf("Ingresá un número entre 1 y 10: ");
        scanf("%d", &numero);
        if (numero >= 1 && numero <= 10)
        {
            printf("Número válido: %d\n", numero);
            continuar = false; // Salir del lazo
        }
        else
        {
            printf("Número fuera de rango. Intentá nuevamente.\n");
        }
    }
    return numero;
}

Simulación con variable de control

Método 3: Función con Retorno Temprano

Para casos más complejos, podés encapsular la lógica en una función y usar return para salir:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <string.h>
void procesar_comandos()
{
    char comando[50];
    printf("Intérprete de comandos (escribí 'salir' para terminar)\n");
    while (1)
    {
        printf(">>> ");
        fgets(comando, sizeof(comando), stdin);
        // Remover salto de línea
        comando[strcspn(comando, "\n")] = 0;
        if (strcmp(comando, "salir") == 0)
        {
            printf("¡Hasta luego!\n");
            return;
        }
        if (strcmp(comando, "ayuda") == 0)
        {
            printf("Comandos disponibles: ayuda, version, salir\n");
        }
        else if (strcmp(comando, "version") == 0)
        {
            printf("Intérprete v1.0\n");
        }
        else if (strlen(comando) > 0)
        {
            printf("Comando desconocido: '%s'\n", comando);
        }
    }
}

Simulación con función y return

Simulación de Otras Estructuras de Control

Simulación de for con while

En algunos contextos educativos o de depuración, puede ser útil convertir lazos for a while. La conversión es también un recordatorio de 0x1003h: Utilizá el lazo for para iteraciones con rango o contador definido y while para lazos controlados por condiciones lógicas: cuando hay un contador y una cota conocida, el for comunica la intención en una sola línea, y el while solo debería aparecer cuando la condición es puramente lógica:

1
2
3
4
5
6
7
8
9
10
11
12
13
// lazo for original:
// for (int i = 0; i < n; i++) {
//     // código del lazo
// }
// Simulación equivalente con while:
{
    int i = 0; // Inicialización
    while (i < n)
    { // Condición
        // código del lazo
        i++; // Incremento
    }
}

Conversión de for a while

Simulación de switch con if-else

Para casos donde switch no está disponible o es preferible evitarlo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
typedef enum {
    OPCION_NUEVA_PARTIDA,
    OPCION_CARGAR_PARTIDA,
    OPCION_CONFIGURACION,
    OPCION_SALIR
} opcion_menu_t;
void procesar_opcion_menu(opcion_menu_t opcion)
{
    if (opcion == OPCION_NUEVA_PARTIDA)
    {
        printf("Iniciando nueva partida...\n");
    }
    else if (opcion == OPCION_CARGAR_PARTIDA)
    {
        printf("Cargando partida guardada...\n");
    }
    else if (opcion == OPCION_CONFIGURACION)
    {
        printf("Abriendo configuración...\n");
    }
    else if (opcion == OPCION_SALIR)
    {
        printf("Saliendo del juego...\n");
    }
    else
    {
        printf("Opción inválida: %d\n", opcion);
    }
}

Simulación de switch con if-else encadenados

Patrones Avanzados de Simulación

Máquina de Estados sin switch

Para sistemas complejos que requieren máquinas de estado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <stdio.h>
typedef enum {
    ESTADO_MENU,
    ESTADO_JUGANDO,
    ESTADO_PAUSA,
    ESTADO_GAME_OVER,
    ESTADO_SALIR,
    ESTADO_MAX
} estado_juego_t;
typedef struct
{
    estado_juego_t estado_actual;
    int puntuacion;
    int vidas;
} contexto_juego_t;
// Prototipos de funciones de estado
void manejar_estado_menu(contexto_juego_t *ctx);
void manejar_estado_jugando(contexto_juego_t *ctx);
void manejar_estado_pausa(contexto_juego_t *ctx);
void manejar_estado_game_over(contexto_juego_t *ctx);
// Tabla de funciones para simular switch
typedef void (*funcion_estado_t)(contexto_juego_t *);
static funcion_estado_t tabla_estados[ESTADO_MAX] = {
    [ESTADO_MENU] = manejar_estado_menu,
    [ESTADO_JUGANDO] = manejar_estado_jugando,
    [ESTADO_PAUSA] = manejar_estado_pausa,
    [ESTADO_GAME_OVER] = manejar_estado_game_over};
void ejecutar_maquina_estados(contexto_juego_t *ctx)
{
    while (ctx->estado_actual != ESTADO_SALIR)
    {
        if (ctx->estado_actual < ESTADO_MAX &&
            tabla_estados[ctx->estado_actual])
        {
            tabla_estados[ctx->estado_actual](ctx);
        }
        else
        {
            fprintf(stderr, "Estado inválido: %d\n", ctx->estado_actual);
            ctx->estado_actual = ESTADO_SALIR;
        }
    }
}
void manejar_estado_menu(contexto_juego_t *ctx)
{
    printf("=== MENÚ PRINCIPAL ===\n");
    printf("1. Jugar\n2. Salir\n");
    int opcion;
    printf("Selecciona opción: ");
    scanf("%d", &opcion);
    if (opcion == 1)
    {
        ctx->estado_actual = ESTADO_JUGANDO;
        ctx->puntuacion = 0;
        ctx->vidas = 3;
    }
    else if (opcion == 2)
    {
        ctx->estado_actual = ESTADO_SALIR;
    }
}
void manejar_estado_jugando(contexto_juego_t *ctx)
{
    printf("Jugando... Puntuación: %d, Vidas: %d\n", ctx->puntuacion,
           ctx->vidas);
    // Simular eventos del juego
    ctx->puntuacion += 10;
    if (ctx->puntuacion >= 100)
    {
        printf("¡Ganaste!\n");
        ctx->estado_actual = ESTADO_GAME_OVER;
    }
    else
    {
        // Continuar jugando o ir a menú
        printf("Presiona 1 para continuar, 2 para ir al menú: ");
        int opcion;
        scanf("%d", &opcion);
        if (opcion == 2)
        {
            ctx->estado_actual = ESTADO_MENU;
        }
    }
}
void manejar_estado_pausa(contexto_juego_t *ctx)
{
    printf("Juego en pausa. Presiona cualquier tecla para continuar...\n");
    getchar();
    ctx->estado_actual = ESTADO_JUGANDO;
}
void manejar_estado_game_over(contexto_juego_t *ctx)
{
    printf("=== GAME OVER ===\n");
    printf("Puntuación final: %d\n", ctx->puntuacion);
    ctx->estado_actual = ESTADO_MENU;
}

Máquina de estados con tabla de funciones

Simulación de lazos Anidados con Funciones

Para evitar lazos anidados complejos que violan la regla de claridad. Fijate que cada lazo de rango se escribe con for y no con un contador manual, en línea con 0x1003h: Utilizá el lazo for para iteraciones con rango o contador definido y while para lazos controlados por condiciones lógicas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdbool.h>
#define FILAS 3
#define COLUMNAS 4
// En lugar de lazos anidados complejos:
void procesar_matriz_compleja_anidado(int matriz[FILAS][COLUMNAS])
{
    for (int i = 0; i < FILAS; i++)
    {
        for (int j = 0; j < COLUMNAS; j++)
        {
            if (matriz[i][j] > 0)
            {
                for (int k = 0; k < matriz[i][j]; k++)
                {
                    // Lógica compleja aquí...
                    printf("Procesando elemento [%d][%d], iteración %d\n", i,
                           j, k);
                }
            }
        }
    }
}
// Simulación con funciones separadas:
void procesar_elemento(int fila, int columna, int valor)
{
    if (valor <= 0)
        return;
    for (int k = 0; k < valor; k++)
    {
        printf("Procesando elemento [%d][%d], iteración %d\n", fila, columna,
               k);
    }
}
void procesar_fila(int matriz[COLUMNAS], int fila)
{
    for (int j = 0; j < COLUMNAS; j++)
    {
        procesar_elemento(fila, j, matriz[j]);
    }
}
void procesar_matriz_compleja_funcional(int matriz[FILAS][COLUMNAS])
{
    for (int i = 0; i < FILAS; i++)
    {
        procesar_fila(matriz[i], i);
    }
}

Desensamblado de lazos anidados

Técnicas para Evitar goto

Aunque goto puede ser útil en casos específicos, su uso puede complicar el flujo del programa y por eso 0x1006h: No utilizar la instrucción goto lo prohíbe. Las alternativas de esta sección muestran cómo estructurar la salida y la limpieza sin saltos:

Método 1: Funciones de Limpieza

El ejemplo comentado usa el patrón de goto cleanup. Ese salto hacia adelante a un único bloque terminal es la excepción tolerada por 0x1011h: Prohibición de saltos no estructurados goto fuera del patrón canónico de liberación de recursos, y su etiqueta debe ir en la columna 1 según 0x0013h: Detector de etiquetas de salto goto no alineadas al margen izquierdo. La versión estructurada que sigue evita el salto con una función de limpieza reutilizable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdio.h>
#include <stdlib.h>
// Problemático con goto:
// int procesar_archivo_con_goto(const char *nombre) {
//     FILE *archivo = NULL;
//     char *buffer = NULL;
//     int resultado = -1;
//
//     archivo = fopen(nombre, "r");
//     if (!archivo) goto cleanup;
//
//     buffer = malloc(1024);
//     if (!buffer) goto cleanup;
//
//     // Procesar archivo...
//     resultado = 0;
//
// cleanup:
//     if (buffer) free(buffer);
//     if (archivo) fclose(archivo);
//     return resultado;
// }
// Alternativa sin goto:
typedef struct
{
    FILE *archivo;
    char *buffer;
    int resultado;
} recursos_t;
void limpiar_recursos(recursos_t *recursos)
{
    if (recursos->buffer)
    {
        free(recursos->buffer);
        recursos->buffer = NULL;
    }
    if (recursos->archivo)
    {
        fclose(recursos->archivo);
        recursos->archivo = NULL;
    }
}
int procesar_archivo_sin_goto(const char *nombre)
{
    recursos_t recursos = {0};
    recursos.archivo = fopen(nombre, "r");
    if (!recursos.archivo)
    {
        limpiar_recursos(&recursos);
        return -1;
    }
    recursos.buffer = malloc(1024);
    if (!recursos.buffer)
    {
        limpiar_recursos(&recursos);
        return -1;
    }
    // Procesar archivo...
    printf("Procesando archivo: %s\n", nombre);
    recursos.resultado = 0;
    limpiar_recursos(&recursos);
    return recursos.resultado;
}

Evitar goto con funciones de limpieza

Método 2: Banderas de Estado

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdbool.h>
int procesar_datos_complejos()
{
    bool error_ocurrido = false;
    int resultado = 0;
    // Paso 1
    if (!error_ocurrido)
    {
        printf("Ejecutando paso 1...\n");
        if (/* condición de error */)
        {
            error_ocurrido = true;
            resultado = -1;
        }
    }
    // Paso 2
    if (!error_ocurrido)
    {
        printf("Ejecutando paso 2...\n");
        if (/* otra condición de error */)
        {
            error_ocurrido = true;
            resultado = -2;
        }
    }
    // Paso 3
    if (!error_ocurrido)
    {
        printf("Ejecutando paso 3...\n");
        // Lógica final
        resultado = 1; // éxito
    }
    if (error_ocurrido)
    {
        printf("Error durante el procesamiento: código %d\n", resultado);
    }
    else
    {
        printf("Procesamiento completado exitosamente.\n");
    }
    return resultado;
}

Uso de banderas en lugar de goto

Consideraciones de Rendimiento

Impacto en la Optimización del Compilador

Las simulaciones pueden afectar las optimizaciones automáticas del compilador:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
// Versión optimizable (compilador puede desenrollar el lazo)
void lazo_simple_optimizable(int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", i);
    }
}
// Versión menos optimizable (lógica más compleja para el compilador)
void lazo_simulado_complejo(int n)
{
    int i = 0;
    bool continuar = true;
    while (continuar)
    {
        printf("%d ", i);
        i++;
        if (i >= n)
        {
            continuar = false;
        }
    }
}
// Versión equilibrada (clara para humanos, optimizable para compilador)
void lazo_simulado_optimizable(int n)
{
    int i = 0;
    while (i < n)
    {
        printf("%d ", i);
        i++;
    }
}

Consideraciones de rendimiento

Medición de Rendimiento

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <time.h>
void benchmark_lazos(int iteraciones)
{
    clock_t inicio, fin;
    // Benchmark lazo for nativo
    inicio = clock();
    for (int i = 0; i < iteraciones; i++)
    {
        // Operación trivial para evitar optimización completa
        volatile int temp = i * 2;
    }
    fin = clock();
    double tiempo_for = ((double)(fin - inicio)) / CLOCKS_PER_SEC;
    // Benchmark simulación while
    inicio = clock();
    int i = 0;
    while (i < iteraciones)
    {
        volatile int temp = i * 2;
        i++;
    }
    fin = clock();
    double tiempo_while = ((double)(fin - inicio)) / CLOCKS_PER_SEC;
    printf("Rendimiento para %d iteraciones:\n", iteraciones);
    printf("  lazo for:     %.6f segundos\n", tiempo_for);
    printf("  Simulación while: %.6f segundos\n", tiempo_while);
    printf("  Diferencia:    %.2f%%\n",
           ((tiempo_while - tiempo_for) / tiempo_for) * 100);
}

Comparación de rendimiento entre técnicas

Mejores Prácticas para Simulación

1. Prioridad de Claridad

Siempre preferí la construcción más clara y natural del lenguaje. En la versión “compleja”, el if sin llaves viola 0x1001h: Todas las estructuras de control deben utilizar llaves, el break como único corte viola el espíritu de 0x1002h: Restringí el uso de break y continue; preferí lazos con bandera de control, y el while que en realidad cuenta iteraciones desaprovecha el for que pide 0x1003h: Utilizá el lazo for para iteraciones con rango o contador definido y while para lazos controlados por condiciones lógicas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Innecesariamente complejo
void ejemplo_malo()
{
    int i = 0;
    while (1)
    {
        if (i >= 10)
            break;
        printf("%d\n", i);
        i++;
    }
}
// Claro y directo
void ejemplo_bueno()
{
    for (int i = 0; i < 10; i++)
    {
        printf("%d\n", i);
    }
}

Priorizar claridad sobre simulación

2. Documentación de Intención

Cuando uses simulación, documentá el porqué:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Simula comportamiento do...while para mantener compatibilidad
 * con compiladores que no soportan C99.
 *
 * Comportamiento equivalente:
 * do {
 *     procesar_entrada(&entrada);
 * } while (!entrada.es_valida);
 */
void validar_entrada_compatible()
{
    entrada_t entrada;
    while (1)
    {
        procesar_entrada(&entrada);
        if (entrada.es_valida)
        {
            break;
        }
    }
}

Documentación de intención en simulaciones

3. Pruebas de Equivalencia

Verificá que la simulación sea equivalente al comportamiento original. El do ... while de referencia escribe su cuerpo entre llaves, como exige 0x100Eh: Delimitación obligatoria con bloque de llaves en lazos do-while (aplicación particular de 0x1001h: Todas las estructuras de control deben utilizar llaves al do-while):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void test_equivalencia_do_while()
{
    int contador_original = 0;
    int contador_simulado = 0;
    // Comportamiento original (do...while)
    int i = 0;
    do
    {
        contador_original++;
        i++;
    } while (i < 5);
    // Simulación
    i = 0;
    while (1)
    {
        contador_simulado++;
        i++;
        if (!(i < 5))
        {
            break;
        }
    }
    // Verificar equivalencia
    assert(contador_original == contador_simulado);
    printf("Simulación verificada: %d == %d iteraciones\n", contador_original,
           contador_simulado);
}

Verificación de equivalencia

Casos de Uso Específicos

1. Menús Interactivos

El menú combina una bandera salir en la condición con un continue acotado para la entrada inválida. Ese continue es el uso restringido que admite 0x1002h: Restringí el uso de break y continue; preferí lazos con bandera de control; cuando la salida es la causa principal, conviene que viaje en la bandera y no en un break suelto. Todos los cuerpos van entre llaves (0x1001h: Todas las estructuras de control deben utilizar llaves):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
typedef enum {
    MENU_OPCION_NUEVA_TAREA = 1,
    MENU_OPCION_LISTAR_TAREAS,
    MENU_OPCION_COMPLETAR_TAREA,
    MENU_OPCION_SALIR
} opcion_menu_t;
void mostrar_menu()
{
    printf("\n=== GESTOR DE TAREAS ===\n");
    printf("1. Nueva tarea\n");
    printf("2. Listar tareas\n");
    printf("3. Completar tarea\n");
    printf("4. Salir\n");
    printf("Selecciona opción: ");
}
void ejecutar_menu_principal()
{
    int opcion;
    bool salir = false;
    while (!salir)
    {
        mostrar_menu();
        if (scanf("%d", &opcion) != 1)
        {
            // Limpiar buffer en caso de entrada inválida
            while (getchar() != '\n')
                ;
            printf("Entrada inválida. Usa números del 1 al 4.\n");
            continue;
        }
        if (opcion == MENU_OPCION_NUEVA_TAREA)
        {
            printf("Creando nueva tarea...\n");
        }
        else if (opcion == MENU_OPCION_LISTAR_TAREAS)
        {
            printf("Listando tareas...\n");
        }
        else if (opcion == MENU_OPCION_COMPLETAR_TAREA)
        {
            printf("Completando tarea...\n");
        }
        else if (opcion == MENU_OPCION_SALIR)
        {
            printf("¡Hasta luego!\n");
            salir = true;
        }
        else
        {
            printf("Opción inválida: %d\n", opcion);
        }
    }
}

Menú interactivo robusto

2. Procesamiento de Archivos con Manejo de Errores

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <errno.h>
#include <string.h>
typedef enum {
    RESULTADO_EXITO,
    RESULTADO_ERROR_APERTURA,
    RESULTADO_ERROR_LECTURA,
    RESULTADO_ERROR_PROCESAMIENTO
} resultado_procesamiento_t;
resultado_procesamiento_t procesar_archivo_robusto(const char *ruta)
{
    FILE *archivo = NULL;
    char linea[256];
    int linea_numero = 0;
    bool error_encontrado = false;
    resultado_procesamiento_t resultado = RESULTADO_EXITO;
    // Intentar abrir archivo
    archivo = fopen(ruta, "r");
    if (!archivo)
    {
        printf("Error abriendo '%s': %s\n", ruta, strerror(errno));
        return RESULTADO_ERROR_APERTURA;
    }
    printf("📖 Procesando archivo: %s\n", ruta);
    // Simular do...while para procesar líneas
    while (1)
    {
        if (!fgets(linea, sizeof(linea), archivo))
        {
            if (feof(archivo))
            {
                break; // Fin de archivo normal
            }
            else
            {
                printf("Error leyendo línea %d: %s\n", linea_numero + 1,
                       strerror(errno));
                resultado = RESULTADO_ERROR_LECTURA;
                error_encontrado = true;
                break;
            }
        }
        linea_numero++;
        // Simular procesamiento de línea
        if (strlen(linea) == 0)
        {
            continue; // Saltar líneas vacías
        }
        // Remover salto de línea
        linea[strcspn(linea, "\n")] = 0;
        printf("  Línea %d: %s\n", linea_numero, linea);
        // Simular condición de error en procesamiento
        if (strstr(linea, "ERROR") != NULL)
        {
            printf("Error en contenido, línea %d: %s\n", linea_numero, linea);
            resultado = RESULTADO_ERROR_PROCESAMIENTO;
            error_encontrado = true;
            break;
        }
    }
    fclose(archivo);
    if (!error_encontrado)
    {
        printf("Archivo procesado exitosamente: %d líneas\n", linea_numero);
    }
    return resultado;
}

Procesamiento robusto de archivos

Ejemplo integrador: de saltos sueltos a control estructurado

Hasta acá viste cada técnica por separado. Este ejemplo las combina en una sola refactorización. La función recorre hasta max líneas de un archivo, cuenta las válidas y corta si se acumulan demasiados errores; en todos los casos debe cerrar el archivo.

La versión ❌ mezcla las violaciones: un goto hacia atrás (0x1006h: No utilizar la instrucción goto y 0x1011h: Prohibición de saltos no estructurados goto fuera del patrón canónico de liberación de recursos), una etiqueta con sangría que el detector de 0x0013h: Detector de etiquetas de salto goto no alineadas al margen izquierdo marcaría, un if sin llaves (0x1001h: Todas las estructuras de control deben utilizar llaves), break y continue como únicos mecanismos de corte (0x1002h: Restringí el uso de break y continue; preferí lazos con bandera de control) y un while que en realidad cuenta iteraciones (0x1003h: Utilizá el lazo for para iteraciones con rango o contador definido y while para lazos controlados por condiciones lógicas).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdbool.h>
#include <stdio.h>

int procesar(const char *ruta, int max)
{
    int leidas = 0;
    int errores = 0;
    char linea[128];
    FILE *archivo = fopen(ruta, "r");
    if (!archivo)
        return -1;

    reintentar:
    while (leidas < max)
    {
        if (fgets(linea, sizeof(linea), archivo) == NULL)
            break;
        if (linea[0] == '#')
            continue;
        if (!validar(linea))
            errores++;
        leidas++;
        if (errores > 3)
            goto reintentar;
    }
    if (archivo)
        fclose(archivo);
    return leidas;
}

❌ Versión con múltiples violaciones de estilo

La versión ✅ reescribe la función sin un solo salto no estructurado. El for agrupa contador y cota (0x1003h: Utilizá el lazo for para iteraciones con rango o contador definido y while para lazos controlados por condiciones lógicas); las banderas tope_errores y fin_archivo llevan las causas de salida a la condición (0x1002h: Restringí el uso de break y continue; preferí lazos con bandera de control); cada cuerpo va entre llaves (0x1001h: Todas las estructuras de control deben utilizar llaves); y la limpieza se delega a cerrar_archivo, de modo que no hace falta ningún goto (0x1006h: No utilizar la instrucción goto).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdbool.h>
#include <stdio.h>

static void cerrar_archivo(FILE **archivo)
{
    if (*archivo)
    {
        fclose(*archivo);
        *archivo = NULL;
    }
}

int procesar(const char *ruta, int max)
{
    bool tope_errores = false;
    bool fin_archivo = false;
    int leidas = 0;
    int errores = 0;
    char linea[128];
    FILE *archivo = fopen(ruta, "r");
    if (!archivo)
    {
        return -1;
    }
    for (int i = 0; i < max && !tope_errores && !fin_archivo; i++)
    {
        if (fgets(linea, sizeof(linea), archivo) == NULL)
        {
            fin_archivo = true;
        }
        else if (linea[0] != '#')
        {
            if (!validar(linea))
            {
                errores++;
            }
            leidas++;
            if (errores > 3)
            {
                tope_errores = true;
            }
        }
    }
    cerrar_archivo(&archivo);
    return leidas;
}

✅ Versión estructurada equivalente

Cuando el patrón canónico de limpieza con goto sea inevitable, recordá que 0x1011h: Prohibición de saltos no estructurados goto fuera del patrón canónico de liberación de recursos solo tolera un salto hacia adelante a un bloque terminal único, y 0x0013h: Detector de etiquetas de salto goto no alineadas al margen izquierdo exige que esa etiqueta vaya en la columna 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int leer_con_limpieza(const char *ruta)
{
    FILE *archivo = fopen(ruta, "r");
    if (!archivo)
    {
        goto limpieza;
    }
    // ... uso del archivo ...
limpieza:
    if (archivo)
    {
        fclose(archivo);
    }
    return 0;
}

Patrón de limpieza tolerado con etiqueta al margen

Si usás un do ... while nativo, el cuerpo también va entre llaves para cumplir 0x100Eh: Delimitación obligatoria con bloque de llaves en lazos do-while:

1
2
3
4
5
6
7
8
9
10
int leer_confirmacion(void)
{
    int respuesta;
    do
    {
        printf("¿Continuar? (1 = sí, 0 = no): ");
        scanf("%d", &respuesta);
    } while (respuesta != 0 && respuesta != 1);
    return respuesta;
}

do-while con cuerpo delimitado

Diagnóstico y refactorización

Usá esta tabla como guía de lectura: identificá el síntoma en tu código, ubicá la regla que lo modela y aplicá la técnica de esta guía.

ReglaSíntoma en el códigoTécnica de esta guía
0x1001h: Todas las estructuras de control deben utilizar llavesUn if, for o while con una sola sentencia sin llaves; al agregar una línea, queda fuera del control.Delimitar todo cuerpo con bloque, como en la versión ✅ del ejemplo integrador.
0x1002h: Restringí el uso de break y continue; preferí lazos con bandera de controlwhile cuya condición no refleja la causa real de salida porque un break o un continue corta antes.Variable de control booleana (Método 2) y banderas tope_errores/fin_archivo.
0x1003h: Utilizá el lazo for para iteraciones con rango o contador definido y while para lazos controlados por condiciones lógicaswhile con i++ escondido en el cuerpo para recorrer un rango o un contador.Conversión de for a while y uso de for en los recorridos de rango.
0x1006h: No utilizar la instrucción gotoEtiquetas y saltos que obligan a reconstruir el flujo como un grafo.Funciones de limpieza, banderas de estado y retornos tempranos.
0x1011h: Prohibición de saltos no estructurados goto fuera del patrón canónico de liberación de recursosgoto hacia atrás, a etiquetas intermedias, o que puede liberar dos veces un recurso.Patrón canónico de limpieza con un único bloque terminal y cerrar_archivo.
0x100Eh: Delimitación obligatoria con bloque de llaves en lazos do-whiledo sentencia while (condicion); donde el while final parece un lazo aparte.do ... while nativo con llaves explícitas en la verificación de equivalencia.
0x0013h: Detector de etiquetas de salto goto no alineadas al margen izquierdoEtiqueta de goto indentada, indistinguible de una sentencia del cuerpo.Etiqueta limpieza: en la columna 1 en el ejemplo integrador.

Ejercicios

Checklist de verificación

Antes de dar por cerrada la refactorización, verificá: