Comme d'autres l'ont dit, le problème ne vient pas de goto
lui - même. le problème réside dans la manière dont les utilisateurs utilisent goto
et rend difficile la compréhension et la maintenance du code.
Supposons le fragment de code suivant:
i = 4;
label: printf( "%d\n", i );
Quelle est la valeur imprimée i
? Quand est-il imprimé? Tant que vous n'avez pas rendu compte de chaque instance de goto label
votre fonction, vous ne pouvez pas savoir. La simple présence de cette étiquette détruit votre capacité à déboguer du code par simple inspection. Pour les petites fonctions avec une ou deux branches, ce n’est pas un problème. Pour les petites fonctions ...
Il y a bien longtemps, au début des années 90, on nous a donné une pile de code C pilotant un affichage graphique 3D et lui demandant de l'exécuter plus rapidement. Il ne s’agissait que d’environ 5000 lignes de code, mais l’ intégralité se trouvait dans main
l’auteur, et l’auteur en utilisait une quinzaine de goto
branches dans les deux sens. C’était un mauvais code pour commencer, mais la présence de ceux-ci goto
le rendait bien pire. Il a fallu environ deux semaines à mon collègue pour comprendre le flux de contrôle. Mieux encore, ces modifications ont goto
abouti à un code si étroitement associé à lui-même que nous ne pouvions apporter aucun changement sans casser quelque chose.
Nous avons essayé de compiler avec une optimisation de niveau 1, et le compilateur a consommé toute la mémoire vive disponible, puis tous les échanges disponibles, puis nous avons paniqué le système (qui n'avait probablement rien à voir avec les goto
s eux-mêmes, mais j'aime bien jeter cette anecdote là-bas).
Au final, nous avons donné deux options au client: laissez-nous réécrire tout à partir de zéro ou achetez du matériel plus rapide.
Ils ont acheté du matériel plus rapide.
Règles de Bode pour utiliser goto
:
- Branche avant seulement;
- Ne pas contourner les structures de contrôle (c.-à-d. Ne pas se brancher dans le corps d'un
if
ou for
ou d'une while
déclaration);
- Ne pas utiliser
goto
à la place d'une structure de contrôle
Il y a des cas où a goto
est la bonne réponse, mais ils sont rares (sortir d'une boucle profondément imbriquée est à peu près le seul endroit où je l'emploierais).
MODIFIER
En développant cette dernière déclaration, voici l’un des rares cas d’utilisation valides pour goto
. Supposons que nous ayons la fonction suivante:
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;
}
Maintenant, nous avons un problème - et si l’un des malloc
appels échouait au milieu? Aussi improbable que cela puisse être, nous ne voulons pas renvoyer un tableau partiellement alloué, nous ne voulons pas non plus renflouer la fonction avec une erreur; nous voulons nettoyer après nous-mêmes et désallouer toute mémoire partiellement allouée. Dans une langue qui lève une exception sur un mauvais allocation, c'est assez simple: vous écrivez simplement un gestionnaire d'exception pour libérer ce qui a déjà été alloué.
En C, vous n’avez pas de gestion structurée des exceptions; vous devez vérifier la valeur de retour de chaque malloc
appel et prendre les mesures appropriées.
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;
}
Peut-on faire cela sans utiliser goto
? Bien sûr, nous pouvons le faire - cela nécessite juste un peu de comptabilité supplémentaire (et, en pratique, c'est le chemin que je prendrais). Mais si vous recherchez des endroits où l'utilisation d'un goto
n'est pas immédiatement un signe de mauvaise pratique ou de mauvaise conception, c'est l'un des rares.