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
La raison d'utiliser gotos est:
- les déclarations inconditionnelles sont plus faciles à comprendre et à suivre
- la nidification est réduite
- les erreurs en ne mettant pas à jour les points de sortie individuels lors de modifications sont empêchées
- enregistre le travail du compilateur pour optimiser le code redondant;)
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:
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
free(var1);
free(var2);
...
free(varN);
return 1;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
free(var1);
free(var2);
...
free(varN);
return 1;
} else {
free(var1);
free(var2);
...
free(varN);
return 0;
}
}
token = strtok(NULL,delim);
}
free(var1);
free(var2);
...
free(varN);
return 1;
Pour moi, le code suivant est beaucoup plus agréable et facile à maintenir si vous devez ajouter un varNplus1
:
int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
retval = 1;
goto out_free;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
retval = 1;
goto out_free;
} else {
retval = 0;
goto out_free;
}
}
token = strtok(NULL,delim);
}
out_free:
free(var1);
free(var2);
...
free(varN);
return retval;
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.
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 == NULL ){
retval = 0;
goto out;
}
if( isValid(var1) ){
retval = some_function(var1);
goto out_free;
}
if( isGood(var1) ){
retval = 0;
goto out_free;
}
out_free:
free(var1);
out:
return retval;
}
Si on considère l'équivalent sans goto
s:
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 != NULL ){
if( isValid(var1) ){
retval = some_function(var1);
} else {
if( isGood(var1) ){
retval = 0;
}
}
free(var1);
} else {
retval = 0;
}
return retval;
}
Pour moi, dans le premier cas, il est évident que si la première fonction revient NULL
, nous sommes sortis d’ici et nous revenons 0
. 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, les free()
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 le free()
faire dans un sous-programme, mais c'est pourquoi le goto 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
, un out_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
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
if( functionC(var1, var2){
count++
continue;
}
...
a bunch of statements
...
count++;
free(var1);
free(var2);
}
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érer var1
et var2
. Je devais ajouter a var3
, 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:
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
var3 = newFunction(line,count);
if( functionC(var1, var2){
goto next;
}
...
a bunch of statements
...
next:
count++;
free(var1);
free(var2);
}
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 le free()
'et les count++
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
#include <stddef.h>
#include <string.h>
char *
strtok(s, delim)
register char *s;
register const char *delim;
{
register char *spanp;
register int c, sc;
char *tok;
static char *last;
if (s == NULL && (s = last) == NULL)
return (NULL);
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
*/
cont:
c = *s++;
for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
if (c == sc)
goto cont;
}
if (c == 0) { /* no non-delimiter characters */
last = NULL;
return (NULL);
}
tok = s - 1;
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
*/
for (;;) {
c = *s++;
spanp = (char *)delim;
do {
if ((sc = *spanp++) == c) {
if (c == 0)
s = NULL;
else
s[-1] = 0;
last = s;
return (tok);
}
} while (sc != 0);
}
/* NOTREACHED */
}
S'il vous plaît, corrigez-moi si je me trompe, mais je pense que l' cont:
étiquette et la goto 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 faisant
while( isDelim(*s++,delim));
pour 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.
- Si, dans une boucle while, vous avez une action continue, le corps de la boucle while doit être une fonction et l'instruction continue doit être une instruction return.
- Si, dans une boucle while, vous avez une instruction break, alors la boucle while elle-même doit être une fonction et la rupture doit devenir une instruction return.
- Si vous avez les deux, alors quelque chose ne va pas.
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.