web-dev-qa-db-fra.com

Pourquoi certains programmeurs du noyau utilisent goto au lieu de simples boucles while?

Quand j'ai appris le C, le professeur m'a dit toute la journée: "Ne pas utiliser goto, c'est une mauvaise habitude, c'est moche, c'est dangereux!" etc.

Pourquoi alors, certains programmeurs du noyau utilisent goto, par exemple dans cette fonction , où il pourrait être remplacé par un simple

while(condition) {} 

ou

do {} while(condition);

Je ne peux pas comprendre ça. Est-il préférable dans certains cas d'utiliser goto au lieu de while/do-while? Et si oui, pourquoi?

52
musicmatze

Dans le cas de cet exemple, je soupçonne qu'il s'agissait de transformer le support SMP en code qui a été écrit à l'origine de manière non sécurisée SMP. Ajout d'un goto again; path est beaucoup plus simple et moins invasif que la restructuration de la fonction.

Je ne peux pas dire que j'aime beaucoup ce style, mais je pense aussi qu'il est erroné d'éviter goto pour des raisons idéologiques. Un cas particulier d'utilisation de goto (différent de cet exemple) est celui où goto n'est utilisé que pour avancer dans une fonction, jamais en arrière. Cette classe d'utilisations n'aboutit jamais à des constructions de boucles issues de goto, et c'est presque toujours la manière la plus simple et la plus claire d'implémenter le comportement nécessaire (qui est généralement le nettoyage et le retour sur erreur).

28
R..

Contexte historique: Nous devons nous rappeler que Dijkstra a écrit Goto considéré comme nuisible en 1968, lorsque de nombreux programmeurs ont utilisé goto en remplacement de programmation structurée (if, while, for, etc.).

C'est 44 ans plus tard, et il est rare de trouver cette utilisation de goto dans la nature. La programmation structurée a déjà gagné depuis longtemps.

Analyse de cas:

L'exemple de code ressemble à ceci:

    SETUP...
again:
    COMPUTE SOME VALUES...
    if (cmpxchg64(ptr, old_val, val) != old_val)
        goto again;

La version structurée ressemble à ceci:

SETUP...
do {
    COMPUTE SOME VALUES...
} while (cmpxchg64(ptr, old_val, val) != old_val);

Quand je regarde la version structurée, je pense immédiatement, "c'est une boucle". Quand je regarde la version goto, je la considère comme une ligne droite avec un cas "réessayer" à la fin.

La version goto a à la fois SETUP et COMPUTE SOME VALUES sur la même colonne, ce qui souligne que la plupart du temps, le flux de contrôle passe par les deux. La version structurée met SETUP et COMPUTE SOME VALUES sur différentes colonnes, ce qui souligne que le contrôle peut les traverser différemment.

La question ici est de savoir quel type d’accent souhaitez-vous mettre dans le code? Vous pouvez comparer cela avec goto pour la gestion des erreurs:

Version structurée:

if (do_something() != ERR) {
    if (do_something2() != ERR) {
        if (do_something3() != ERR) {
            if (do_something4() != ERR) {
                ...

Version Goto:

if (do_something() == ERR)  // Straight line
    goto error;             // |
if (do_something2() == ERR) // |
    goto error;             // |
if (do_something3() == ERR) // |
    goto error;             // V
if (do_something4() == ERR) // emphasizes normal control flow
    goto error;

Le code généré est fondamentalement le même, nous pouvons donc le considérer comme une préoccupation typographique, comme l'indentation.

56
Dietrich Epp

Très bonne question, et je pense que seuls les auteurs peuvent apporter une réponse définitive. J'ajouterai ma part de spéculation en disant qu'il aurait pu commencer à l'utiliser pour la gestion des erreurs, comme expliqué par @Izkata et les portes étaient alors ouvertes à l'utiliser également pour les boucles de base.

À mon avis, l'utilisation de la gestion des erreurs est légitime dans la programmation des systèmes. Une fonction alloue progressivement de la mémoire au fur et à mesure de sa progression, et si une erreur se produit, elle goto l'étiquette appropriée pour libérer les ressources dans l'ordre inverse à partir de ce point.

Ainsi, si l'erreur se produit après la première allocation, elle passera à la dernière étiquette d'erreur, pour libérer une seule ressource. De même, si l'erreur se produit après la dernière allocation, il passera à la première étiquette d'erreur et s'exécutera à partir de là, libérant toutes les ressources jusqu'à la fin de la fonction. Un tel modèle de gestion des erreurs doit encore être utilisé avec précaution, en particulier lorsque la modification du code, valgrind et les tests unitaires sont fortement recommandés. Mais il est sans doute plus lisible et maintenable que les approches alternatives.

Une règle d'or de l'utilisation de goto est d'éviter le soi-disant code spaghetti. Essayez de tracer des lignes entre chaque instruction goto et son étiquette respective. Si vous avez des lignes qui se croisent, eh bien, vous avez franchi une ligne :). Une telle utilisation de goto est très difficile à lire et une source commune de bogues difficiles à suivre, car ils seraient trouvés dans des langages comme BASIC qui comptaient sur lui pour le contrôle de flux.

Si vous ne faites qu'une seule boucle, vous n'obtiendrez pas de lignes croisées, donc c'est toujours lisible et maintenable, devenant largement une question de style. Cela dit, comme ils peuvent être aussi facilement effectués avec les mots clés de boucle fournis par la langue, comme vous l'avez indiqué dans votre question, ma recommandation serait toujours d'éviter d'utiliser goto pour les boucles, simplement parce que le for, do/while ou while les constructions sont plus élégantes de par leur conception.

1
Nagev