Como otros han dicho, el problema no está en goto
sí mismo; El problema es cómo usan las personas goto
y cómo puede dificultar la comprensión y el mantenimiento del código.
Suponga el siguiente fragmento de código:
i = 4;
label: printf( "%d\n", i );
¿Para qué valor se imprime i
? ¿Cuándo se imprime? Hasta que no tenga en cuenta cada instancia de goto label
su función, no puede saberlo. La simple presencia de esa etiqueta destruye su capacidad de depurar código mediante una simple inspección. Para funciones pequeñas con una o dos ramas, no es un gran problema. Para funciones no pequeñas ...
A principios de los años 90, nos dieron una pila de código C que manejaba una pantalla gráfica en 3D y nos dijo que hiciera que funcionara más rápido. Solo tenía alrededor de 5000 líneas de código, pero todo estaba dentro main
, y el autor usó aproximadamente 15 goto
ramificaciones en ambas direcciones. Para empezar, este era un código malo , pero la presencia de esos goto
s lo hizo mucho peor. A mi compañero de trabajo le tomó alrededor de 2 semanas descifrar el flujo de control. Aún mejor, esos goto
s resultaron en un código tan fuertemente acoplado consigo mismo que no pudimos hacer ningún cambio sin romper algo.
Intentamos compilar con la optimización de nivel 1, y el compilador consumió toda la RAM disponible, luego todo el intercambio disponible, y luego entró en pánico en el sistema (que probablemente no tuvo nada que ver con los goto
s, pero me gusta lanzar esa anécdota).
Al final, le dimos al cliente dos opciones: reescribir todo desde cero o comprar hardware más rápido.
Compraron hardware más rápido.
Reglas de Bode para usar goto
:
- Ramificarse solo hacia adelante;
- No pasar por alto las estructuras de control (es decir, no se ramifican en el cuerpo de un
if
o for
o while
declaración);
- No utilizar
goto
en lugar de una estructura de control.
Hay casos en los que a goto
es la respuesta correcta, pero son raros (salir de un bucle profundamente anidado es el único lugar donde lo usaría).
EDITAR
Ampliando esa última declaración, este es uno de los pocos casos de uso válidos para goto
. Supongamos que tenemos la siguiente función:
T ***myalloc( size_t N, size_t M, size_t P )
{
size_t i, j, k;
T ***arr = malloc( sizeof *arr * N );
for ( i = 0; i < N; i ++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
for ( j = 0; j < M; j++ )
{
arr[i][j] = malloc( sizeof *arr[i][j] * P );
for ( k = 0; k < P; k++ )
arr[i][j][k] = initial_value();
}
}
return arr;
}
Ahora, tenemos un problema: ¿qué sucede si una de las malloc
llamadas falla a la mitad? No es probable que sea un evento como ese, no queremos devolver una matriz parcialmente asignada, ni queremos simplemente abandonar la función con un error; queremos limpiar después de nosotros mismos y desasignar cualquier memoria parcialmente asignada. En un lenguaje que arroja una excepción en una mala asignación, eso es bastante sencillo: simplemente escribe un controlador de excepciones para liberar lo que ya se ha asignado.
En C, no tiene un manejo estructurado de excepciones; debe verificar el valor de retorno de cada malloc
llamada y tomar las medidas apropiadas.
T ***myalloc( size_t N, size_t M, size_t P )
{
size_t i, j, k;
T ***arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( i = 0; i < N; i ++ )
{
if ( !(arr[i] = malloc( sizeof *arr[i] * M )) )
goto cleanup_1;
for ( j = 0; j < M; j++ )
{
if ( !(arr[i][j] = malloc( sizeof *arr[i][j] * P )) )
goto cleanup_2;
for ( k = 0; k < P; k++ )
arr[i][j][k] = initial_value();
}
}
}
goto done;
cleanup_2:
// We failed while allocating arr[i][j]; clean up the previously allocated arr[i][j]
while ( j-- )
free( arr[i][j] );
free( arr[i] );
// fall through
cleanup_1:
// We failed while allocating arr[i]; free up all previously allocated arr[i][j]
while ( i-- )
{
for ( j = 0; j < M; j++ )
free( arr[i][j] );
free( arr[i] );
}
free( arr );
arr = NULL;
done:
return arr;
}
¿Podemos hacer esto sin usar goto
? Por supuesto que podemos, solo requiere un poco de contabilidad adicional (y, en la práctica, ese es el camino que tomaría). Pero, si está buscando lugares donde usar un goto
no sea inmediatamente una señal de mala práctica o diseño, este es uno de los pocos.