O guia de estilo linux fornece razões específicas para o uso de gotoacordo com o seu exemplo:
https://www.kernel.org/doc/Documentation/process/coding-style.rst
A lógica para usar gotos é:
- declarações incondicionais são mais fáceis de entender e seguir
- aninhamento é reduzido
- erros ao não atualizar pontos de saída individuais ao impedir modificações
- salva o trabalho do compilador para otimizar o código redundante;)
Isenção de responsabilidade Não devo compartilhar meu trabalho. Os exemplos aqui são um pouco inventados, então tenha paciência comigo.
Isso é bom para o gerenciamento de memória. Recentemente, trabalhei em códigos que alocavam memória dinamicamente (por exemplo, um char *retornado por uma função). Uma função que analisa um caminho e verifica se o caminho é válido analisando os tokens do caminho:
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;
Agora, para mim, o código a seguir é muito melhor e mais fácil de manter, se você precisar adicionar um 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;
Agora, o código tinha todos os tipos de problemas com ele, ou seja, N estava acima de 10, e a função tinha mais de 450 linhas, com 10 níveis de aninhamento em alguns lugares.
Mas eu ofereci ao meu supervisor para refatorá-lo, o que eu fiz e agora são várias funções curtas, e todas elas têm o estilo 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;
}
Se considerarmos o equivalente sem gotos:
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;
}
Para mim, no primeiro caso, é óbvio para mim que, se a primeira função retornar NULL, estaremos fora daqui e voltaremos 0. No segundo caso, eu tenho que rolar para baixo para ver que o if contém toda a função. Concedido o primeiro indica isso estilisticamente (o nome " out") e o segundo faz sintaticamente. O primeiro é ainda mais óbvio.
Além disso, eu prefiro ter free()instruções no final de uma função. Isso ocorre em parte porque, na minha experiência, as free()declarações no meio de funções cheiram mal e indicam para mim que eu deveria criar uma sub-rotina. Nesse caso, eu criei var1em minha função e não free()consegui em uma sub-rotina, mas é por isso que o goto out_freeestilo de sair é tão prático.
Eu acho que os programadores precisam ser educados acreditando que gotosão maus. Então, quando estiverem maduros o suficiente, devem procurar o código fonte do Linux e ler o guia de estilo do Linux.
Devo acrescentar que uso esse estilo de maneira muito consistente, toda função tem um int retval, um out_freelabel e um out label. Devido à consistência estilística, a legibilidade é aprimorada.
Bônus: quebra e continua
Digamos que você tenha um loop 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);
}
Há outras coisas erradas nesse código, mas uma coisa é a instrução continue. Eu gostaria de reescrever a coisa toda, mas fui encarregado de modificá-la em um pequeno caminho. Levaria dias para refatorá-lo de uma maneira que me satisfizesse, mas a mudança real foi de cerca de meio dia de trabalho. O problema é que, mesmo se nós ' continue' ainda precisamos libertar var1e var2. Eu tive que adicionar um var3, e isso me fez querer vomitar ter que espelhar as declarações free ().
Eu era um estagiário relativamente novo na época, mas estava olhando o código-fonte do Linux há algum tempo, então perguntei ao meu supervisor se eu poderia usar uma declaração goto. Ele disse que sim, e eu fiz isso:
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);
}
Eu acho que continua bom, na melhor das hipóteses, mas para mim eles são como um goto com uma etiqueta invisível. O mesmo vale para pausas. Eu ainda preferiria continuar ou interromper, a menos que, como foi o caso aqui, o force a espelhar modificações em vários lugares.
E devo acrescentar que esse uso goto next;e o next:rótulo são insatisfatórios para mim. Eles são meramente melhores do que espelhar o free()'s e as count++declarações.
gotoQuase sempre estão errados, mas é preciso saber quando eles são bons de usar.
Uma coisa que não discuti é o tratamento de erros, coberto por outras respostas.
atuação
Pode-se observar a implementação 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 */
}
Por favor, corrija-me se estiver errado, mas acredito que o cont:rótulo e a goto cont;declaração existem para desempenho (eles certamente não tornam o código mais legível). Eles podem ser substituídos por código legível fazendo
while( isDelim(*s++,delim));
pular delimitadores. Mas, para ser o mais rápido possível e evitar chamadas de função desnecessárias, elas fazem dessa maneira.
Eu li o jornal de Dijkstra e acho bastante esotérico.
google "dijkstra goto declaração considerada prejudicial" porque não tenho reputação suficiente para postar mais de 2 links.
Eu o vi citado como uma razão para não usar o goto e a leitura dele não mudou nada, na medida em que meus usos do goto são considerados.
Adendo :
Eu criei uma regra clara enquanto pensava em tudo isso sobre continuações e interrupções.
- Se em um loop while, você tem uma continuação, o corpo do loop while deve ser uma função e a continuação uma declaração de retorno.
- Se em um loop while, você possui uma instrução break, o próprio loop while deve ser uma função e a interrupção deve se tornar uma declaração de retorno.
- Se você tiver os dois, algo pode estar errado.
Nem sempre é possível devido a problemas de escopo, mas descobri que isso facilita muito o raciocínio sobre meu código. Eu havia notado que sempre que um loop de tempo interrompia ou continuava, isso me dava um mau pressentimento.