Goto : une instruction pas comme les autres ?
Le débat autour de l'instruction refait surface suite au « Goto fail » d'iOS et de GnuTLS
Le 2014-03-14 08:38:30, par Arsene Newman, Expert éminent sénior
Suite à la découverte récente de bugs à la fois sous Linux et sous iOS/OS X, tous les deux dus à une utilisation de l’instruction Goto du langage C, la question de l’utilisation de cette instruction refait surface.
Goto est une instruction héritée des premiers langages informatiques, époque où certaines instructions très connues actuellement n’existaient pas, comme les boucles et les structures de contrôle. Néanmoins, même à l’âge moderne de l’informatique et après avoir été décriée par plusieurs informaticiens de renom à l’instar de Djikistra, cette instruction est encore utilisée et cela même au sein des géants de l’informatique comme Apple ; à titre d’exemple la recherche du mot clé Goto sur un portail tel que GitHub débouche sur des millions de résultats.
Alors dans quel cas cette instruction est encore utilisée ? Parmi les cas de figure évoqués existent la sortie d’une boucle imbriquée ce qui épargne le recours à plusieurs break, l’amélioration de la lisibilité du code et le traitement des erreurs ou encore l’optimisation manuelle du code pour améliorer les performances.
Toutefois, alors qu’une utilisation parcimonieuse dans les deux premiers cas semble correcte, cela s’avère incorrect pour le dernier cas, selon Jeff Law et Jason Merril, tous les deux ingénieurs chez Red Hat et membres du comité du compilateur GCC. En effet ils expliquent que l’optimisation manuelle du code n’est plus à l’ordre du jour car les compilateurs modernes sont suffisamment développés pour se charger gracieusement de cette tâche, en transformant le code en entrée en une série de blocs de base et en se reposant sur l’utilisation d’un graphe de flot de contrôle (GFC). Résultat des courses aucune distinction entre un code bien structuré et un code en spaghetti (qui résulte d’une addiction au goto).
Autre argument en faveur de cette thèse, l’article du célèbre Donald Knuth sur le sujet à savoir « Structured Programming with Go To Statements », où il explique qu’une optimisation prématurée du code ne peut déboucher que sur une utilisation malsaine de Goto.
Au final, la recommandation de l’utilisation du Goto n’est toujours pas à l’ordre du jour, mais cette instruction héritée des premiers âges de l’informatique garde encore sa place dans des cas précis où certains langages, à l’image du langage C, affichent des manques et des carences, ce qui ne cadre pas avec un quelconque souci d’optimisation manuelle du code.
Source : billet de Larry Seltzer
Et vous ?
Qu’en pensez-vous ?
Goto est une instruction héritée des premiers langages informatiques, époque où certaines instructions très connues actuellement n’existaient pas, comme les boucles et les structures de contrôle. Néanmoins, même à l’âge moderne de l’informatique et après avoir été décriée par plusieurs informaticiens de renom à l’instar de Djikistra, cette instruction est encore utilisée et cela même au sein des géants de l’informatique comme Apple ; à titre d’exemple la recherche du mot clé Goto sur un portail tel que GitHub débouche sur des millions de résultats.
Alors dans quel cas cette instruction est encore utilisée ? Parmi les cas de figure évoqués existent la sortie d’une boucle imbriquée ce qui épargne le recours à plusieurs break, l’amélioration de la lisibilité du code et le traitement des erreurs ou encore l’optimisation manuelle du code pour améliorer les performances.
Toutefois, alors qu’une utilisation parcimonieuse dans les deux premiers cas semble correcte, cela s’avère incorrect pour le dernier cas, selon Jeff Law et Jason Merril, tous les deux ingénieurs chez Red Hat et membres du comité du compilateur GCC. En effet ils expliquent que l’optimisation manuelle du code n’est plus à l’ordre du jour car les compilateurs modernes sont suffisamment développés pour se charger gracieusement de cette tâche, en transformant le code en entrée en une série de blocs de base et en se reposant sur l’utilisation d’un graphe de flot de contrôle (GFC). Résultat des courses aucune distinction entre un code bien structuré et un code en spaghetti (qui résulte d’une addiction au goto).
Autre argument en faveur de cette thèse, l’article du célèbre Donald Knuth sur le sujet à savoir « Structured Programming with Go To Statements », où il explique qu’une optimisation prématurée du code ne peut déboucher que sur une utilisation malsaine de Goto.
Au final, la recommandation de l’utilisation du Goto n’est toujours pas à l’ordre du jour, mais cette instruction héritée des premiers âges de l’informatique garde encore sa place dans des cas précis où certains langages, à l’image du langage C, affichent des manques et des carences, ce qui ne cadre pas avec un quelconque souci d’optimisation manuelle du code.
Source : billet de Larry Seltzer
Et vous ?
-
ObsidianModérateurCe n'est pas lié au goto, mais au fait qu'une ligne s'est trouvée dupliquée. 'faudrait voir à ne pas troller plus encore qu'on le fait déjà à ce sujet. Il aurait bien pu se passer la même chose avec un point-virgule mal placé, ce qui est assez fréquent en C.Goto est une instruction héritée des premiers langages informatiques, époque où certaines instructions très connues actuellement n’existaient pas, comme les boucles et les structures de contrôles. Néanmoins, même à l’âge moderne de l’informatique et après avoir été décrié par plusieurs informaticiens de renom à l’instar de Djikistra, cette instruction est encore utilisée et cela même au sein des géants de l’informatique comme Apple, à titre d’exemple la recherche du mot clé Goto sur un portail tel que GitHub débouche sur des millions de résultats.Alors dans quel cas cette instruction est encore utilisée ? Parmi les cas de figure évoqués existent la sortie d’une boucle imbriquée ce qui épargne le recours à plusieurs break, l’amélioration de la lisibilité du code et le traitement des erreurs ou encore l’optimisation manuelle du code pour améliorer les performances.
1. La possibilité de sauter dans une boucle pour la faire démarrer ailleurs qu'au début, ce qui permet de gérer efficacement les cas du style « 1 + le reste » très utilisés en informatique et dans les séries mathématiques. Par exemple, pour écrire une suite de chiffres séparés par des tirets :Code : 1
2
3
4
5
6
7
8x=1; goto debut; while (x<=5) { putchar ('-'); debut: printf ("%d",x); x++; }
On remarque que ça pourrait tout-à-fait être un modèle de boucle à part entière : de même qu'il existe « continue » en C pour provoquer le saut immédiat vers la fin de la boucle et lui faire faire une itération, on pourrait tout-à-fait imaginer un mot-clé « start » servant à faire cela.
2. Les automates finis à états. Le goto est par nature l'âme d'un AFD, puisqu'il s'agit de sauter d'un état prédéterminé à un autre. Si le cheminement du traitement est déterminé à l'avance, il est tout-à-fait possible d'écrire :Code : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15etat1: traitement1(); if (condition) goto etat2; if (condition) goto etat3; if (condition) goto etat5; goto etat6; etat2: traitement2(); if (condition) goto etat6; if (condition) goto etat4; if (condition) goto etat1; goto etat2; …
Ce que l'on reproche au goto, donc, c'est surtout d'être généralement incompatible avec la programmation structurée. Mais ça, c'est un paradigme qui doit être pensé par le programmeur avant tout. De la même façon qu'il est possible d'avoir une approche orientée objet en C même si le langage n'est pas spécialement conçu pour cela au départ, il est possible d'écrire un programme propre avec des gotos si le programmeur le souhaite. L'ennui est qu'il est généralement impossible de le faire admettre à son entourage direct et qu'en entreprise, il est plus facile d'écrire du code sale mais « orthodoxe » plutôt qu'avoir à justifier son goto.Toutefois alors qu’une utilisation parcimonieuse dans les deux premiers cas semble correcte, cela ne s’avère pas correcte pour le dernier cas selon Jeff Law et Jason Merril tous les deux ingénieurs chez Red Hat et membre du comité du compilateur GCC, en effet ils expliquent que l’optimisation manuelle du code n’est plus à l’ordre du jour car les compilateurs modernes sont suffisamment développés pour se charger gracieusement de cette tâche, en transformant le code en entrée en une série de blocs de base et en se reposant sur l’utilisation d’un graphe de flot de contrôle (GFC), résultat des courses aucune distinction entre un code bien structuré et un code en spaghetti (qui résulte d’une addiction au goto).
Ensuite, au niveau du compilateur, c'est vrai mais dans une certaine mesure également. Il est vrai que les compilateurs sont devenus particulièrement performants mais le fait est qu'on leur prête souvent des pouvoirs magiques et que les gens qui se penchent réellement sur la qualité du code produit sont bien peu nombreux.Au final, la recommandation de l’utilisation du Goto n’est toujours pas à l’ordre du jour mais cette instruction héritée des premiers âges de l’informatique garde encore sa place dans des cas précis où certains langages à l’image du langage C affichent des manques et des carences, ce qui ne cadre pas avec un quelconque souci d’optimisation manuelle du code.
Un vrai débat de fond, voire un sujet d'études scientifique, serait d'auditer un grand nombre de programmes chez les codeurs de tous niveaux et vérifier si, à chaque fois, la solution alternative adoptée est réellement meilleure. Mais ça…le 14/03/2014 à 11:32 -
ObsidianModérateurLa vraie question est : pourquoi ça te pique les yeux (à part le fait que c'est ce que l'on t'a toujours dit) ? Les réponses existent et elles sont à la base de ce débat, mais elles sont discutables en fonction de la situation et surtout, plus personne ne se pose cette question, même au départ.
Et c'est bien le problème : l'objectif des codeurs n'est plus d'écrire le code le propre possible en fonction du contexte mais bien d'éviter les goto par principe.Ok pour le deuxième mais le premier se réécrit :Code : 1
2
3
4
5
6
7
8printf ("%d",1); x=2; while (x<5) { putchar ('-'); printf ("%d",x); x++; }
Dans cet exemple, tu saisis deux fois « printf() » mais tu n'établis aucune relation logique entre les deux : le compilateur n'a aucun moyen de savoir que ton premier printf() est en fait censé faire partie de la même boucle, ce qui casse complètement le principe-même de la programmation structurée. Ensuite, même si ce n'était pas nécessaire, tu as changé la condition initiale (x=2 et plus x=1) et quitte à écrire printf("%d",1);, tu aurais pu directement écrire printf("1". Donc, des erreurs en cascade induites par la volonté de se priver du goto et qui, au final, rendent le code moins bon qu'au départ.
Ensuite, dans le cas présent, il n'y a qu'une seule instruction. Que se passerait-il si tu devais sauter les cinquante premières instructions d'une boucle qui en compte cent ? Pour adopter le modèle que tu nous présentes, tu serais obligé de déclarer une fonction pour les appeler facilement avant puis dans la boucle. Et si tu retrouves le même cas de figure trente fois dans ton programme, tu dois déclarer trente fonctions locales. C'est idiot.
C'est également plus pénible pour le développeur : même s'il reconnaît ce que tu es en train de faire, il ne peut pas savoir s'il y a bien une liaison implicite entre tes deux printf. Ça devient critique lorsque tu fais la maintenance d'un grand programme : lorsque tu en vient à modifier ce printf pour le mettre à jour ou le remplacer par autre chose, tu introduis automatiquement un bug si tu ne penses pas à traiter l'autre.
Il est intéressant, lorsque l'on développe du logiciel, d'essayer de transposer cela au reste de l'industrie et, en particulier, de penser à la manière dont on réaliserait la fonction de façon mécanique plutôt que logicielle : dans le cas présent, si je veux imprimer sur une feuille une suite de motifs séparés par des tirets, il me suffit de construire un simple rouleau d'imprimerie et de le « déphaser » de manière à ce qu'il commence par le motif plutôt que par le tiret. La solution que tu nous proposes consisterait, elle, à mettre en place une machine spéciale dédiée pour imprimer le premier motif uniquement avant de faire passer normalement la feuille dans le rouleau.
Tout cela pour éviter un goto qui aurait eu tout-à-fait sa place ici.
C'est intéressant parce qu'en général, on en arrive à dire « de toutes façons le compilateur va optimiser tout cela ». Personne ne nous le garantit, d'une part, et cela revient à dire que le vrai travail est en fait mené par les personnes qui ont conçu le compilateur. Et même alors, le compilateur sera à même de faire cette optimisation que s'il est capable de reconnaître le modèle. Il faut donc que celui-ci soit défini au départ et fasse partie des motifs qui lui ont été enseignés. En toute rigueur, c'est surtout ce travail que le programmeur devrait faire, et utiliser les goto si le langage ne propose pas de lui-même ce modèle.
C'était justement le problème de départ : éviter de se trimballer un « if » évalué à chaque itération alors qu'il n'est là que pour tester le premier cas.le 14/03/2014 à 12:56 -
MédinocExpert éminent séniorPourquoi tant de gens ici parlent de "goto pour optimisation"?
Les deux bugs récents proviennent de codes où l'usage de goto n'avait rien à voir avec l'optimisation, et tout à voir avec une gestion d'erreur+nettoyage plus lisible (sans code boomerang et sans duplication du nettoyage à chaque sortie).
Ce qui est à ma connaissance le cas le plus reconnu pour l'utilisation de goto en C.
Et franchement, la source du bug était plutôt une étiquette dont le nom ne correspondait pas au contenu (le "fail" du bug Apple faisait en fait un "cleanup"que le goto lui-même. le 14/03/2014 à 14:26 -
ManusDeiExpert confirméLa non-utilisation du GOTO n'est pas une restriction, mais une tradition, nuance.
Et il faudrait faire attention à ne pas en faire un dogme, car il existe quelques rares cas où il est indispensables, et d'autres où il est quand même bien plus clair.
PS : Sinon on devrait interdire les pointeurs manuels et leur manipulation, car niveau casse-gueule, les pointeurs c'est pas mal non plus (ça et la gestion manuelle de la mémoire).le 14/03/2014 à 14:58 -
picodevMembre émériteBonjour,
cela faisait longtemps aussi que je n'avait répondu à un fil sur le goto !
Après m'être frotté avec des machines allant du TI99/4A à l'atari 512stf, J'ai enfin reçu une «vraie» formation informatique en fac au courant des années 1990. C'est à ce moment qu'on m'a révélé les commandements que je ne devrais jamais violer au risque de m'exposer à la colère divine. Je peux citer des choses comme «la bande passante du réseau illimitée tu ne croiras point» ou «de courtes fonctions tu écriras» sans oublier le «ton code tu commenteras». Mais il est vrai que LA loi première était «JAMAIS de goto tu n'utiliseras», LA loi qui évite de rentrer dans un état de péché originel ... le goto est comme une pomme qu'il ne faut pas croquer sous peine d'expulsion du jardin de l'Eden de la programmation structurée.
J'obéissais militairement à ces lois dans le but d'être adoubé mais essayais néanmoins d'en comprendre l'origine. Jusqu'au jour où j'ai reçu l'illumination de la part d'un de mes professeur, M. Cansell (true story).
Le goto peut introduire de la confusion dans la phase de compilation, il interrompt le flux sans qu'on puisse a priori savoir ce qui va se passer réellement ou quelle était l'intention du programmeur (du moins à l'époque). Écrire une boucle for en utilisant des gotos masquait le fait de vouloir utiliser une boucle for et empêchait le compilo de faire du bon boulot. C'est pourquoi il était préférable de toujours utiliser une boucle for ou while, ou toute autre structure de plus haut niveau pour se passer de goto. Enfin en général, car les gurus du goto pouvaient optimiser certains codes grâce à eux, mais comme nous ne sommes que des mortels faillibles et non des gurus ....
Et effectivement, au fur et à mesure, les cas d'utilisation de goto se sont retrouvées de moins en moins nombreux car remplacés par de nouvelles structure de niveau supérieur : switch (goto calculé), gestion des erreurs, etc ...
La règle est alors amendée : si le langage utilisé ne propose pas la structure de contrôle adaptée alors le goto avec des pincettes tu pourras utiliser. Par exemple le C ne propose pas de gestion d'erreur par try/catch ... ok on peut utiliser les goto si c'est indispensable, C ne propose pas un «ne fait pas ça pour au premier tour de boucle» du coup un goto est «autorisé» et même élégant dans ce cas je trouve car ça évite une duplication de code (encore une loi ... jamais inutilement du code tu ne dupliqueras).
Le second point est, comme le soulève Obsidian, celui des automates finis, et plus généralement des parsers. Les machines à états avec transitions s'implémentent agréablement avec des gotos ... mais souvent la taille des programmes est telle que le code devient complexe sans pour autant être compliqué. La solution la plus simple est donc de créer un programme (et de prouver qu'il est correct) qui va s"en charger pour nous. Lex,flex,bison et yacc tirent leur origine de ça et créent du code bourré de goto (enfin dans les années 90...) tout à fait correct (la plupart du temps dans les années 90).
Bref, un goto c'est comme une paire de ciseaux pointus : on se blesse plus souvent qu'avec une paire de ciseaux arrondis.le 15/03/2014 à 14:35 -
ObsidianModérateurAvant tout : je m'aperçois en me relisant que mon ton était peut-être un peu sec. Mes excuses à ce que j'aurais pu blesser, ce n'était pas intentionnel. Nous sommes bien en train de disserter sur le fond et rien d'autre.
Oui mais justement : le premier tour de boucle ne constitue pas une phase d'initialisation. C'est la même que celle des autres tours et l'une et l'autre doivent être modifiées en même temps si c'est nécessaire.Et si il faut introduire des fonctions alors c'est d'accord pour moi.( pour le printf("%d",1);, on est d'accord c'est un exemple écrit vite fait) Il manque des commentaires expliquant que algorithmes à deux partis inutilisation et boucle. Et bien sûr il ne faudrait pas des 5 codé en dur...J'évite les goto ( sauf boucles imbriqués ) par principe mais pas pour ces raisons là.
Maintenant, comme on l'a dit également, la sortie de boucle est un modèle qui marche très bien en C mais qui n'est pas du tout universel et qui pose problème justement avec les langages interprétés, à commencer par le BASIC où le goto, s'il n'en est pas carrément issu, est censé être roi.le 14/03/2014 à 14:03 -
GrimlyMembre avertiL'instruction GOTO ou JUMP suivant comment chacun l'a appris est à la base de nos structures IF, FOR, WHILE et UNTIL.
Il est tout aussi possible de réaliser des bugs avec une instruction GOTO en l'utilisant mal qu'avec une structure WHILE.
Il reste à chacun d'utiliser ce avoir quoi il est le plus à l'aise et confiant. Blâmer l'un ou l'autre n'a aucun sens.
On pourrait aussi parler de l'utilité des instructions CONTINUE et BREAK disponibles dans la majorité des langage largement utilisés, qui ne sont que des GOTO avec comme restriction de n'agir que dans le cadre d'une instruction FOR, WHILE ou UNTIL.le 14/03/2014 à 14:20 -
Même un if then else sera compilé avec des goto, le but premier des langages ( structurés ) est de simplifier le code source en supprimant les goto qui pique les yeux...
Ok pour le deuxième mais le premier se réécrit :Code : 1
2
3
4
5
6
7
8printf ("%d",1); x=2; while (x<5) { putchar ('-'); printf ("%d",x); x++; }
le 14/03/2014 à 12:29 -
germinolegrandMembre expertPour ma part je suis en train de faire des tests en C++, et le goto s'en sort vraiment très bien au milieu des exceptions et des destructeurs.
J'en écrirai plus long sur le sujet quand j'aurai suffisamment fait de tests, mais pour l'instant il me semble que l'on pourrait (en C++, pas en C attention) le dédiaboliser, car il apporte la solution à un certains nombre de problèmes qui sont actuellement résolus par des workaround bien moches à coups de boucles.le 14/03/2014 à 14:11 -
CodeurPlusPlusEn attente de confirmation mailLes pointeurs c'est la liberté. Dans un langage avec pointeur, le programmeur décide si un type de données qu'il crée est un pointeur explicite, un pointeur implicite ou pas un pointeur du tout. Dans un langage sans pointeur, la règle est la sémantique par référence uniforme, et il faut s'y plier que cela nous arrange ou non.
Quand j'étais étudiant, un de nos profs avait écrit un module en pur langage C qui offrait une implémentation tout à fait bonne des exceptions dans ce langage. Quand on regardait le code de ce module, il y avait quelques goto dedans, et il aurait été difficile de s'en passer.
Cela dit évidemment qu'un programmeur n'a généralement pas besoin d'utiliser des gotos.
EDIT : l'intervention de 11h32 de Obsidian est hyper intéressante. J'ai appris des trucs.le 14/03/2014 à 15:41