GNU/Linux >> Tutoriels Linux >  >> Linux

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

Contexte historique : Rappelons-nous que Dijkstra a écrit Goto Considered Harmful en 1968, quand beaucoup de programmeurs utilisaient goto en remplacement de la 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é, il y a 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 le goto version, je la considère comme une ligne droite avec un cas "réessayer" à la fin.

Le goto la version 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 voulez-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) {
                ...

Aller à la version :

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 un problème typographique, comme l'indentation.


Très bonne question, et je pense que seul(s) l'auteur(s) peut fournir une réponse définitive. J'ajouterai ma petite spéculation en disant qu'il aurait pu commencer par l'utiliser pour la gestion des erreurs, comme l'a expliqué @Izkata et que les portes étaient alors ouvertes pour l'utiliser également pour les boucles de base.

L'utilisation de la gestion des erreurs est légitime dans la programmation système, à mon avis. Une fonction alloue progressivement de la mémoire au fur et à mesure de sa progression, et si une erreur est rencontrée, elle sera 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 ne libérer qu'une seule ressource. De même, si l'erreur se produit après la dernière allocation, elle sautera au premier libellé 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 lors de 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 pour utiliser goto est d'éviter le soi-disant code spaghetti. Essayez de tracer des lignes entre chaque goto déclaration 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 courante de bogues difficiles à suivre, car ils se trouveraient dans des langages comme BASIC qui en dépendaient pour le contrôle de flux.

Si vous ne faites qu'une seule boucle simple, vous n'obtiendrez pas de lignes de franchissement, il est donc toujours lisible et maintenable, devenant en grande partie une question de style. Cela dit, comme ils peuvent tout aussi facilement être réalisé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.


Dans le cas de cet exemple, je soupçonne qu'il s'agissait de mettre à niveau le support SMP dans du code qui a été écrit à l'origine d'une manière non sécurisée pour SMP. Ajouter un goto again; est beaucoup plus simple et moins invasif que de restructurer la fonction.

Je ne peux pas dire que j'aime beaucoup ce style, mais je pense aussi qu'il est malavisé d'éviter goto pour des raisons idéologiques. Un cas particulier de goto l'utilisation (différente de cet exemple) est où goto sert uniquement à avancer dans une fonction, jamais à reculer. Cette classe d'utilisations n'entraîne jamais de constructions de boucle issues de goto , et c'est presque toujours le moyen le plus simple et le plus clair d'implémenter le comportement nécessaire (qui consiste généralement à nettoyer et à revenir en cas d'erreur).


Linux
  1. Pourquoi Ubuntu 14.04 Lts utilise-t-il une version du noyau non-lts ?

  2. Erreur "carte en cours d'utilisation" lors de la suppression d'un périphérique multivoie dans CentOS/RHEL

  3. Pourquoi certains programmeurs du noyau utilisent-ils goto au lieu de simples boucles while ?

  4. Pourquoi eval devrait-il être évité dans Bash et que dois-je utiliser à la place ?

  5. pourquoi u8 u16 u32 u64 est-il utilisé à la place d'un entier non signé dans la programmation du noyau

Pourquoi j'utilise exa au lieu de ls sous Linux

Pourquoi j'utilise rxvt comme terminal

Comment utiliser les boucles dans Ansible Playbook

Les 10 meilleures raisons d'utiliser Linux

Pourquoi les nerds utilisent Linux

Linux vs Mac OS :15 raisons d'utiliser Linux au lieu de Mac OS