J'hésite vraiment à poser cette question, car je ne souhaite pas "solliciter un débat, des arguments, des sondages ou des discussions prolongées", mais je suis novice en langage C et souhaite mieux comprendre les modèles courants utilisés dans le langage.
J'ai récemment entendu du dégoût pour la goto
commande, mais j'ai aussi récemment trouvé un cas d'utilisation décent pour cette commande.
Code comme ceci:
error = function_that_could_fail_1();
if (!error) {
error = function_that_could_fail_2();
if (!error) {
error = function_that_could_fail_3();
...to the n-th tab level!
} else {
// deal with error, clean up, and return error code
}
} else {
// deal with error, clean up, and return error code
}
Si la partie nettoyage est très similaire, vous pourriez écrire un peu plus joli (mon avis?) Comme ceci:
error = function_that_could_fail_1();
if(error) {
goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code
Est-ce un cas d'utilisation courant ou acceptable goto
en C? Existe-t-il un moyen différent / meilleur de le faire?
c
design-patterns
goto
Robz
la source
la source
goto hell;
Réponses:
L'
goto
instruction (et ses étiquettes correspondantes) est une primitive de contrôle de flux (ainsi que l'exécution conditionnelle d'une instruction). Je veux dire par là qu’ils sont là pour vous permettre de construire des réseaux de contrôle de flux de programmes. Vous pouvez les considérer comme une modélisation des flèches entre les nœuds d'un diagramme.Certaines d'entre elles peuvent être optimisées immédiatement, lorsqu'il existe un flux linéaire direct (vous utilisez simplement une séquence d'instructions de base). Il est préférable de remplacer les autres modèles par des constructions de programmation structurées lorsque celles-ci sont disponibles; si cela ressemble à une
while
boucle, utilisez unewhile
boucle , OK? Les modèles de programmation structurés sont certainement au moins potentiellement plus clairs d'intention qu'un désordre d'goto
énoncés.Cependant, C n'inclut pas toutes les constructions de programmation structurées possibles. (Je ne vois pas encore si toutes les informations pertinentes ont été découvertes; le rythme de découverte est lent, mais j’hésite à dire que toutes ont été retrouvées.) Parmi ceux que nous connaissons, C manque clairement du
try
/catch
/finally
structure (et exceptions aussi). Il lui manque également unebreak
boucle multi-niveaux . C'est le genre de choses que l'goto
on peut utiliser pour mettre en œuvre. Il est possible d’utiliser d’autres systèmes pour les réaliser également - nous savons que C possède un ensemble suffisant degoto
les primitives - mais elles impliquent souvent la création de variables de drapeau et de conditions de boucle ou de garde beaucoup plus complexes; augmenter l'enchevêtrement de l'analyse de contrôle avec l'analyse des données rend le programme plus difficile à comprendre en général. Cela rend également plus difficile l’optimisation du compilateur et l’exécution rapide de la CPU (la plupart des constructions de contrôle de flux - et certainementgoto
- sont très économiques).Ainsi, bien que vous ne devriez pas utiliser
goto
sauf besoin, vous devez être conscient du fait qu'il existe et qu'il peut être nécessaire, et si vous en avez besoin, vous ne devriez pas vous sentir trop mal. Un exemple de cas où cela est nécessaire est la désallocation de ressource lorsqu'une fonction appelée renvoie une condition d'erreur. (C'est-à-dire,try
/finally
.) Il est possible d'écrire cela sans cela,goto
mais cela peut avoir des inconvénients, tels que des problèmes de maintenance. Un exemple de l'affaire:Le code pourrait être encore plus court, mais c'est suffisant pour démontrer le point.
la source
try/catch/finally
àgoto
malgré la même, encore plus envahissante (car il peut se propager à travers plusieurs fonctions / modules) forme de code spaghetti qui est possible à l' aidetry/catch/finally
?Oui.
Il est utilisé, par exemple, dans le noyau Linux. Voici un email de la fin d'un fil d'il y a presque une décennie , en gras, le mien:
Cela dit, il faut beaucoup de discipline pour éviter de créer du code spaghetti une fois que vous vous êtes habitué à utiliser goto. Par conséquent, à moins d'écrire quelque chose qui nécessite de la vitesse et une faible empreinte mémoire (comme un noyau ou un système intégré), vous devriez vraiment Pensez-y avant d'écrire le premier goto.
la source
À mon avis, le code que vous avez publié est un exemple d'utilisation valide
goto
, car vous sautez uniquement vers le bas et vous ne l'utilisez que comme un gestionnaire d'exception primitif.Cependant , à cause du vieux débat sur le goto, les programmeurs évitent
goto
depuis environ 40 ans et ne sont donc pas habitués à lire du code avec goto. Ceci est une raison valable pour éviter d'aller à: c'est tout simplement pas la norme.J'aurais réécrit le code de manière plus lisible par les programmeurs C:
Avantages de cette conception:
la source
Un document célèbre décrivant des cas d’utilisation valide de était Programmation structurée avec déclaration GOTO de Donald E. Knuth (Université de Stanford). Le document a été publié à une époque où l'utilisation de GOTO était considérée comme un péché par certains et lorsque le mouvement de la programmation structurée était à son apogée. Vous voudrez peut-être jeter un coup d'œil à GoTo Considered Harmful.
la source
En Java, vous le feriez comme ceci:
Je l'utilise beaucoup. Bien que je n'aime pas
goto
trop, dans la plupart des autres langages de style C, j'utilise votre code; il n'y a pas d'autre moyen de le faire. (Sauter hors des boucles imbriquées est un cas similaire; en Java, j'utilise un libellébreak
et partout ailleurs, ungoto
.)la source
Je pense que c'est un cas d'utilisation correcte, mais dans le cas où « erreur » est rien de plus qu'une valeur booléenne, il y a une autre façon d'accomplir ce que vous voulez:
Ceci utilise l'évaluation de court-circuit d'opérateurs booléens. Si ce "meilleur" dépend de vos goûts personnels et de votre habitude à cet idiome.
la source
error
valeur pourrait perdre tout son sens avec tous les OR.Le guide de style linux donne des raisons spécifiques d'utilisation
goto
qui correspondent à votre exemple:https://www.kernel.org/doc/Documentation/process/coding-style.rst
Déni de responsabilité Je ne suis pas supposé partager mon travail. Les exemples ici sont un peu artificiels alors supportez s'il vous plaît avec moi.
C'est bon pour la gestion de la mémoire. J'ai récemment travaillé sur du code qui avait alloué de la mémoire de manière dynamique (par exemple, un
char *
rendu par une fonction). Une fonction qui examine un chemin et vérifie si le chemin est valide en analysant les jetons du chemin:Pour moi, le code suivant est beaucoup plus agréable et facile à maintenir si vous devez ajouter un
varNplus1
:Maintenant, le code avait toutes sortes d'autres problèmes, à savoir que N se situait quelque part au-dessus de 10 et que sa fonction comptait plus de 450 lignes, avec 10 niveaux d'imbrication à certains endroits.
Mais j’ai proposé à mon superviseur de le refactoriser, ce que j’ai fait et c’est maintenant un tas de fonctions qui sont toutes courtes et qui ont toutes le style linux.
Si on considère l'équivalent sans
goto
s:Pour moi, dans le premier cas, il est évident que si la première fonction revient
NULL
, nous sommes sortis d’ici et nous revenons0
. Dans le second cas, je dois faire défiler l'écran vers le bas pour voir que le if contient la fonction entière. Accordé le premier me l'indique stylistiquement (le nom "out
") et le second le fait syntaxiquement. Le premier est encore plus évident.De plus, je préfère grandement avoir des
free()
déclarations à la fin d'une fonction. C'est en partie parce que, selon mon expérience, lesfree()
déclarations au milieu de fonctions ont une odeur nauséabonde et m'indiquent que je devrais créer un sous-programme. Dans ce cas, j'ai créévar1
dans ma fonction et ne pouvais pas lefree()
faire dans un sous-programme, mais c'est pourquoi legoto out_free
style, goto out est si pratique.Je pense que les programmeurs doivent être éduqués en croyant que
goto
c'est mal. Ensuite, quand ils seront suffisamment matures, ils devront parcourir le code source de Linux et lire le guide de style de Linux.J'ajouterais que j'utilise ce style de manière très cohérente, chaque fonction ayant un int
retval
, unout_free
label et un label out. En raison de la cohérence stylistique, la lisibilité est améliorée.Bonus: Pause et continue
Disons que vous avez une boucle while
Il y a d'autres problèmes avec ce code, mais une chose est l'instruction continue. Je voudrais réécrire le tout, mais on m'a demandé de le modifier légèrement. Cela m'aurait pris des jours pour le reformuler de manière à me satisfaire, mais le changement réel a duré environ une demi-journée de travail. Le problème est que même si nous "
continue
, nous devons toujours libérervar1
etvar2
. Je devais ajouter avar3
, et cela me donnait envie de vomir pour refléter les déclarations free ().J'étais un stagiaire relativement nouveau à l'époque, mais je m'intéressais depuis longtemps au code source de Linux, alors j'ai demandé à mon superviseur si je pouvais utiliser une déclaration goto. Il a dit oui et j'ai fait ceci:
Je pense que continuer est au mieux acceptable, mais pour moi, c'est comme un goto avec une étiquette invisible. La même chose vaut pour les pauses. Je préférerais toujours continuer ou interrompre sauf si, comme c'était le cas ici, cela vous oblige à refléter les modifications apportées à plusieurs endroits.
Et je devrais également ajouter que cette utilisation de
goto next;
et l'next:
étiquette ne me satisfont pas. Ils sont simplement meilleurs que de refléter lefree()
'et lescount++
déclarations.goto
ont presque toujours tort, mais il faut savoir quand ils sont bons à utiliser.Une chose dont je n’ai pas parlé est la gestion des erreurs qui a été couverte par d’autres réponses.
Performance
On peut regarder la mise en oeuvre de strtok () http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c
S'il vous plaît, corrigez-moi si je me trompe, mais je pense que l'
cont:
étiquette et lagoto cont;
déclaration sont là pour la performance (ils ne rendent sûrement pas le code plus lisible). Ils pourraient être remplacés par du code lisible en faisantpour ignorer les délimiteurs. Mais pour être aussi rapide que possible et éviter les appels de fonctions inutiles, ils le font de cette façon.
J'ai lu le journal de Dijkstra et je le trouve assez ésotérique.
google "déclaration de dijkstra goto considérée comme nuisible" parce que je n'ai pas assez de réputation pour publier plus de 2 liens.
Je l'ai vu cité comme une raison de ne pas utiliser de goto et sa lecture n'a rien changé en ce qui concerne mes utilisations de goto.
Addenda :
Je suis venu avec une règle bien en pensant à tout cela continue et se casse.
Ce n'est pas toujours possible en raison de problèmes de portée, mais j'ai constaté que cela facilite beaucoup la tâche de raisonnement à propos de mon code. J'avais remarqué que chaque fois qu'une boucle tenait une pause ou continuait, je ressentais un mauvais pressentiment.
la source
Personnellement, je le refrais plus comme ça:
Cela serait plus motivé par l’évitement de l’imbrication profonde que par celui obtenu par goto (IMO, un problème encore plus grave avec le premier exemple de code), et serait bien sûr dépendant du fait que CleanupAfterError soit hors de portée (dans ce cas, "params" pourrait soit une structure contenant de la mémoire allouée que vous devez libérer, un FICHIER * que vous devez fermer ou autre chose).
Un avantage majeur de cette approche réside dans le fait qu’il est à la fois plus simple et plus propre d’intégrer un futur pas supplémentaire hypothétique entre, par exemple, FTCF2 et FTCF3 (ou de supprimer une étape en cours existante), de sorte qu’il se prête mieux à la maintenabilité (et à la personne hérite de mon code ne voulant pas me lyncher!) - mis à part, la version imbriquée ne le manque pas.
la source
Jetez un coup d’œil aux directives de codage C de la MISRA (Association pour la fiabilité des logiciels dans l’industrie automobile) qui permettent à goto de répondre à des critères stricts (auxquels votre exemple correspond
Là où je travaille, le même code serait écrit - pas besoin de rien - Eviter les débats religieux inutiles à leur sujet est un avantage considérable pour toute entreprise de logiciels.
ou pour "goto in drag" - quelque chose d'encore plus douteux que goto, mais qui contourne le "No goto ever !!!" camp) "C’est sûr que ça doit aller, on n’utilise pas Goto" ....
Si les fonctions ont le même type de paramètre, mettez-les dans une table et utilisez une boucle -
la source
if(flag)
en un seul exemplaire prendre le « si » branche et ayant chacune des déclarations correspondantes dans l'autre copie prendre la " autre". Les actions qui définissent et effacent l'indicateur sont en réalité des "gotos" qui basculent entre ces deux versions du code. Il y a des moments où l'utilisation de drapeaux est plus propre que toute autre solution, mais l'ajout d'un drapeau pour sauvegarder unegoto
cible ne constitue pas un bon compromis.J'utilise également
goto
si ledo/while/continue/break
hackery alternatif serait moins lisible.goto
s ont l’avantage que leurs cibles ont un nom et qu’elles lisentgoto something;
. Cela peut être plus lisible quebreak
oucontinue
si vous n'arrêtez ni ne continuez quelque chose.la source
do ... while(0)
construction ou d' une autre, qui n'est pas une boucle réelle mais une tentative effrénée d'empêcher l'utilisation de agoto
.la source
break
fonctionne exactement commegoto
, bien que ne portant pas de stigmatisation.Il y aura toujours des camps qui disent qu'un moyen est acceptable et un autre qui ne l'est pas. Les entreprises pour lesquelles j'ai travaillé ont froncé les sourcils ou fortement découragé leur utilisation. Personnellement, je ne peux penser à aucun moment où j'en ai utilisé un, mais cela ne signifie pas qu'ils sont mauvais , mais une autre façon de faire les choses.
En C, je fais généralement ce qui suit:
Pour le traitement, en utilisant votre exemple goto, je ferais ceci:
error = function_that_could_fail_1 (); if (! error) {error = function_that_could_fail_2 (); } if (! error) {error = function_that_could_fail_3 (); }
Il n'y a pas d'imbrication, et à l'intérieur des clauses if, vous pouvez créer un rapport d'erreur si l'étape générée génère une erreur. Donc, cela ne doit pas forcément être "pire" qu'une méthode utilisant gotos.
Je n'ai pas encore rencontré de cas où quelqu'un a un goto qui ne peut pas être fait avec une autre méthode et qui est tout aussi lisible / compréhensible et qui est la clé, IMHO.
la source