Respostas:
Existem algumas razões para usar a declaração "goto" que eu conheço (algumas já falaram com isso):
Sair de uma função corretamente
Geralmente, em uma função, você pode alocar recursos e precisar sair em vários locais. Os programadores podem simplificar seu código colocando o código de limpeza de recursos no final da função, e todos os "pontos de saída" da função iriam para o rótulo de limpeza. Dessa forma, você não precisa escrever um código de limpeza em todos os "pontos de saída" da função.
Saindo de loops aninhados
Se você estiver em um loop aninhado e precisar interromper todos os loops, um goto poderá tornar isso muito mais limpo e mais simples do que as instruções break e if-checks.
Melhorias de baixo nível de desempenho
Isso é válido apenas no código perf-critical, mas as instruções goto são executadas muito rapidamente e podem dar um impulso ao se mover através de uma função. Essa é uma faca de dois gumes, no entanto, porque um compilador normalmente não pode otimizar código que contém gotos.
Observe que em todos esses exemplos, os gotos são restritos ao escopo de uma única função.
goto
por return
é simplesmente bobo. Não é "refatorar" nada, é apenas "renomear" para que as pessoas que cresceram em um goto
ambiente reprimido (ou seja, todos nós) se sintam melhor em usar o que moralmente significa goto
. Eu prefiro ver o loop em que o uso e ver um pouco goto
, o que por si só é apenas uma ferramenta , do que ver alguém movendo o loop em algum lugar não relacionado apenas para evitar a goto
.
break
, continue
, return
são, basicamente goto
, apenas em embalagem agradável.
do{....}while(0)
deve ser uma idéia melhor do que ir, exceto pelo fato de funcionar em Java.
Todo mundo que é anti- goto
cita, direta ou indiretamente, o artigo GoTo Considered Nocivo de Edsger Dijkstra para fundamentar sua posição. Pena que o artigo de Dijkstra não tenha praticamente nada a ver com o modo como as goto
declarações são usadas atualmente e, portanto, o que o artigo diz tem pouca ou nenhuma aplicabilidade ao cenário de programação moderno. O goto
meme-menos agora se aproxima de uma religião, até suas escrituras ditadas do alto, seus sumos sacerdotes e a rejeição (ou pior) dos hereges percebidos.
Vamos colocar o artigo de Dijkstra em contexto para esclarecer um pouco o assunto.
Quando Dijkstra escreveu seu artigo, as línguas populares da época eram processuais não estruturadas, como BASIC, FORTRAN (os dialetos anteriores) e várias linguagens de montagem. Era bastante comum as pessoas que usavam linguagens de nível superior pularem toda a sua base de código em segmentos de execução distorcidos e distorcidos que deram origem ao termo "código espaguete". Você pode ver isso pulando para o clássico jogo Trek escrito por Mike Mayfield e tentando descobrir como as coisas funcionam. Reserve alguns momentos para examinar isso.
Este é "o uso desenfreado da declaração go to" contra a qual Dijkstra criticou em seu trabalho em 1968. Esse é o ambiente em que ele viveu que o levou a escrever esse trabalho. A capacidade de pular para qualquer lugar que você gosta no seu código, a qualquer momento que você gostava, era o que ele estava criticando e exigindo que fosse interrompido. Comparando isso com os poderes anêmicos do goto
C ou de outras línguas mais modernas, é simplesmente risível.
Eu já posso ouvir os cantos levantados dos cultistas quando eles encaram o herege. "Mas", eles cantam, "você pode dificultar a leitura do código goto
em C." Oh sim? Você também pode dificultar a leitura do código sem ele goto
. Como este:
#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
Não está goto
à vista, então deve ser fácil de ler, certo? Ou que tal este:
a[900]; b;c;d=1 ;e=1;f; g;h;O; main(k,
l)char* *l;{g= atoi(* ++l); for(k=
0;k*k< g;b=k ++>>1) ;for(h= 0;h*h<=
g;++h); --h;c=( (h+=g>h *(h+1)) -1)>>1;
while(d <=g){ ++O;for (f=0;f< O&&d<=g
;++f)a[ b<<5|c] =d++,b+= e;for( f=0;f<O
&&d<=g; ++f)a[b <<5|c]= d++,c+= e;e= -e
;}for(c =0;c<h; ++c){ for(b=0 ;b<k;++
b){if(b <k/2)a[ b<<5|c] ^=a[(k -(b+1))
<<5|c]^= a[b<<5 |c]^=a[ (k-(b+1 ))<<5|c]
;printf( a[b<<5|c ]?"%-4d" :" " ,a[b<<5
|c]);} putchar( '\n');}} /*Mike Laman*/
goto
Também não há. Portanto, deve ser legível.
Qual é o meu ponto com esses exemplos? Não são os recursos de linguagem que tornam o código ilegível e impossível de manter. Não é a sintaxe que faz isso. São programadores ruins que causam isso. E programadores ruins, como você pode ver no item acima, podem tornar qualquer recurso de idioma ilegível e inutilizável. Como os for
laços lá em cima. (Você pode vê-los, certo?)
Agora, para ser justo, algumas construções de linguagem são mais fáceis de abusar do que outras. Se você é um programador de C, no entanto, eu examinaria muito mais de perto cerca de 50% dos usos de #define
muito antes de começar uma cruzada contra goto
!
Portanto, para aqueles que se preocuparam em ler até aqui, há vários pontos importantes a serem observados.
goto
declarações foi escrito para um ambiente de programação em que goto
era muito
mais potencialmente prejudicial do que na maioria das linguagens modernas que não são montadoras.goto
devido a isso é tão racional quanto dizer "Tentei me divertir uma vez, mas não gostei, então agora sou contra".goto
instruções modernas (anêmicas) no código que não podem ser adequadamente substituídos por outras construções.godo
" abominação em que um do
loop sempre falso é interrompido break
no lugar de a goto
. Estes são frequentemente piores do que o uso criterioso de goto
.goto
realmente são (que é a pergunta postada)
Obedecer às melhores práticas cegamente não é uma prática recomendada. A idéia de evitar goto
declarações como forma principal de controle de fluxo é evitar a produção de código espaguete ilegível. Se usadas com moderação nos lugares certos, às vezes podem ser a maneira mais simples e clara de expressar uma ideia. Walter Bright, o criador do compilador Zortech C ++ e da linguagem de programação D, os usa com frequência, mas criteriosamente. Mesmo com as goto
declarações, seu código ainda é perfeitamente legível.
Conclusão: Evitar goto
evitar goto
é inútil. O que você realmente deseja evitar é produzir código ilegível. Se o seu goto
código -laden for legível, não há nada errado com ele.
Como goto
dificulta o raciocínio sobre o fluxo do programa 1 (também conhecido como “código de espaguete”), goto
geralmente é usado para compensar os recursos ausentes: o uso de goto
pode ser realmente aceitável, mas apenas se o idioma não oferecer uma variante mais estruturada para obter o mesmo objetivo. Veja o exemplo da Dúvida:
A regra com goto que usamos é que goto é aceitável para avançar para um único ponto de limpeza de saída em uma função.
Isso é verdade - mas apenas se o idioma não permitir o tratamento de exceções estruturadas com código de limpeza (como RAII ou finally
), que executa o mesmo trabalho melhor (como ele foi especialmente desenvolvido para isso) ou quando há um bom motivo para isso. para empregar manipulação de exceção estruturada (mas você nunca terá esse caso, exceto em um nível muito baixo).
Na maioria dos outros idiomas, o único uso aceitável de goto
é sair de loops aninhados. E mesmo lá, é quase sempre melhor elevar o loop externo para um método próprio e usá-lo return
.
Fora isso, goto
é um sinal de que não há pensamento suficiente no código específico.
1 As linguagens modernas que suportam goto
implementam algumas restrições (por exemplo, goto
podem não entrar ou sair de funções), mas o problema permanece fundamentalmente o mesmo.
A propósito, o mesmo também é verdade para outros recursos de idioma, principalmente as exceções. E geralmente existem regras rígidas para usar apenas esses recursos quando indicado, como a regra de não usar exceções para controlar o fluxo não excepcional do programa.
finally
? Então, usar exceções para outras coisas que não o tratamento de erros é bom, mas o uso goto
é ruim? Eu acho que as exceções são nomeadas adequadamente.
Bem, há uma coisa que é sempre pior do que goto's
; uso estranho de outros operadores de fluxo de programas para evitar ir para:
Exemplos:
// 1
try{
...
throw NoErrorException;
...
} catch (const NoErrorException& noe){
// This is the worst
}
// 2
do {
...break;
...break;
} while (false);
// 3
for(int i = 0;...) {
bool restartOuter = false;
for (int j = 0;...) {
if (...)
restartOuter = true;
if (restartOuter) {
i = -1;
}
}
etc
etc
do{}while(false)
Eu acho que pode ser considerado idiomático. Você não tem permissão para discordar: D
goto after_do_block;
sem realmente dizer isso. Caso contrário ... um "loop" que é executado exatamente uma vez? Eu chamaria isso de abuso de estruturas de controle.
#define
são muitas, muitas vezes muito piores do que usar de goto
vez em quando: D
Em C #, a instrução switch não permite falhas . Portanto, goto é usado para transferir o controle para um rótulo de caixa de seleção específico ou o rótulo padrão .
Por exemplo:
switch(value)
{
case 0:
Console.Writeln("In case 0");
goto case 1;
case 1:
Console.Writeln("In case 1");
goto case 2;
case 2:
Console.Writeln("In case 2");
goto default;
default:
Console.Writeln("In default");
break;
}
Editar: há uma exceção na regra "sem falhas". O fall-through é permitido se uma instrução de caso não tiver código.
goto case 5:
quando está no caso 1). Parece que a resposta de Konrad Rudolph está correta aqui: goto
está compensando um recurso ausente (e é menos claro do que o recurso real seria). Se o que realmente queremos é falha, talvez o melhor padrão não seja falha, mas algo como continue
solicitá-la explicitamente.
#ifdef TONGUE_IN_CHEEK
O Perl tem um goto
que permite implementar chamadas de fuga do pobre homem. :-P
sub factorial {
my ($n, $acc) = (@_, 1);
return $acc if $n < 1;
@_ = ($n - 1, $acc * $n);
goto &factorial;
}
#endif
Ok, então isso não tem nada a ver com C's goto
. Mais seriamente, concordo com os outros comentários sobre o uso goto
de limpezas, a implementação do dispositivo de Duff ou algo semelhante. É uma questão de usar, não abusar.
(O mesmo comentário pode ser aplicado a longjmp
exceções call/cc
e similares - eles têm usos legítimos, mas podem ser facilmente abusados. Por exemplo, lançando uma exceção puramente para escapar de uma estrutura de controle profundamente aninhada, sob circunstâncias completamente não excepcionais .)
Eu escrevi mais do que algumas linhas de linguagem assembly ao longo dos anos. Por fim, toda linguagem de alto nível é compilada em gotos. Ok, chame-os de "galhos" ou "saltos" ou qualquer outra coisa, mas são gotos. Alguém pode escrever assembler sem goto?
Agora, com certeza, você pode apontar para um programador Fortran, C ou BASIC que fazer tumultos com gotos é uma receita para a bolonhesa de espaguete. A resposta, no entanto, não é evitá-los, mas usá-los com cuidado.
Uma faca pode ser usada para preparar comida, libertar alguém ou matar alguém. Nós ficamos sem facas por medo do último? Da mesma forma, o goto: usado descuidadamente atrapalha, usado com cuidado ajuda.
Dê uma olhada em Quando usar Saltar ao programar em C :
Embora o uso do goto seja quase sempre uma prática ruim de programação (certamente você pode encontrar uma maneira melhor de executar o XYZ), há momentos em que realmente não é uma má escolha. Alguns podem até argumentar que, quando é útil, é a melhor escolha.
A maior parte do que tenho a dizer sobre goto realmente se aplica apenas a C. Se você estiver usando C ++, não há motivo para usá-lo no lugar de exceções. Em C, no entanto, você não tem o poder de um mecanismo de tratamento de exceções; portanto, se quiser separar o tratamento de erros do restante da lógica do programa e evitar reescrever o código de limpeza várias vezes em todo o código, então goto pode ser uma boa escolha.
O que eu quero dizer? Você pode ter algum código parecido com este:
int big_function()
{
/* do some work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* clean up*/
return [success];
}
Tudo bem até você perceber que precisa alterar seu código de limpeza. Então você tem que passar e fazer 4 alterações. Agora, você pode decidir que pode simplesmente encapsular toda a limpeza em uma única função; isso não é uma má ideia. Mas isso significa que você precisará ter cuidado com os ponteiros - se você planeja liberar um ponteiro em sua função de limpeza, não há como configurá-lo para apontar para NULL, a menos que você passe um ponteiro para um ponteiro. Em muitos casos, você não usará esse ponteiro novamente, de modo que isso pode não ser uma grande preocupação. Por outro lado, se você adicionar um novo ponteiro, identificador de arquivo ou outra coisa que precise de limpeza, precisará alterar sua função de limpeza novamente; e então você precisará alterar os argumentos para essa função.
Ao usar goto
, será
int big_function()
{
int ret_val = [success];
/* do some work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
end:
/* clean up*/
return ret_val;
}
O benefício aqui é que seu código após o final tem acesso a tudo o que é necessário para executar a limpeza e você conseguiu reduzir consideravelmente o número de pontos de alteração. Outro benefício é que você passou de vários pontos de saída para sua função para apenas um; não há chance de você retornar acidentalmente da função sem limpar.
Além disso, como o goto está sendo usado apenas para pular para um único ponto, não é como se você estivesse criando uma massa de código espaguete pulando para frente e para trás na tentativa de simular chamadas de função. Em vez disso, o goto realmente ajuda a escrever um código mais estruturado.
Em uma palavra, goto
sempre deve ser usado com moderação e como último recurso - mas há um tempo e um lugar para isso. A questão não deve ser "você precisa usá-lo", mas "é a melhor opção" para usá-lo.
Uma das razões pelas quais o Goto é ruim, além do estilo de codificação, é que você pode usá-lo para criar loops sobrepostos , mas não aninhados :
loop1:
a
loop2:
b
if(cond1) goto loop1
c
if(cond2) goto loop2
Isso criaria a estrutura bizarra, mas possivelmente legal, do fluxo de controle em que uma sequência como (a, b, c, b, a, b, a, b, ...) é possível, o que torna os hackers de compiladores infelizes. Aparentemente, existem vários truques inteligentes de otimização que dependem desse tipo de estrutura que não ocorre. (Eu deveria verificar minha cópia do livro do dragão ...) O resultado disso pode (usando alguns compiladores) ser que outras otimizações não são feitas para o código que contém goto
s.
Pode ser útil se você souber apenas "ah, a propósito", por acaso persuade o compilador a emitir código mais rápido. Pessoalmente, eu preferiria tentar explicar ao compilador o que é provável e o que não é antes de usar um truque como o goto, mas é possível que eu tente goto
antes de hackear o assembler.
goto
é útil é que ela permite que você construa loops como esse, que exigiriam várias contorções lógicas caso contrário. Eu diria ainda que, se o otimizador não souber reescrever isso, tudo bem . Um loop como esse não deve ser feito para desempenho ou legibilidade, mas porque é exatamente a ordem na qual as coisas precisam acontecer. Nesse caso, eu particularmente não gostaria que o otimizador estivesse mexendo com ele.
Acho engraçado que algumas pessoas cheguem ao ponto de dar uma lista de casos em que o goto é aceitável, dizendo que todos os outros usos são inaceitáveis. Você realmente acha que conhece todos os casos em que ir é a melhor escolha para expressar um algoritmo?
Para ilustrar, darei um exemplo que ninguém aqui ainda mostrou:
Hoje eu estava escrevendo um código para inserir um elemento em uma tabela de hash. A tabela de hash é um cache de cálculos anteriores que podem ser substituídos à vontade (afetando o desempenho, mas não a correção).
Cada bloco da tabela de hash possui 4 slots, e eu tenho vários critérios para decidir qual elemento sobrescrever quando um bloco estiver cheio. No momento, isso significa fazer até três passes em um balde, assim:
// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if (slot_p[add_index].hash_key == hash_key)
goto add;
// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
goto add;
// Additional passes go here...
add:
// element is written to the hash table here
Agora, se eu não usasse goto, como seria esse código?
Algo assim:
// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if (slot_p[add_index].hash_key == hash_key)
break;
if (add_index >= ELEMENTS_PER_BUCKET) {
// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
break;
if (add_index >= ELEMENTS_PER_BUCKET)
// Additional passes go here (nested further)...
}
// element is written to the hash table here
Ficaria cada vez pior se mais passagens fossem adicionadas, enquanto a versão com goto mantém o mesmo nível de indentação o tempo todo e evita o uso de declarações if espúrias cujo resultado é implícito pela execução do loop anterior.
Portanto, há outro caso em que o goto torna o código mais limpo e mais fácil de escrever e entender ... Tenho certeza de que existem muitos mais, então não finja conhecer todos os casos em que o goto é útil, dissing os bons que você não poderia pense.
goto
é ter cada função no mesmo nível de abstração. Isso evita goto
é um bônus.
container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();
acho esse tipo de programação muito mais fácil de ler. Cada função nesse caso pode ser escrita como uma função pura, o que é muito mais fácil de se raciocinar. A função principal agora explica o que o código faz apenas pelo nome das funções e, se você quiser, pode ver as definições deles para descobrir como ele o faz.
A regra com goto que usamos é que goto é aceitável para avançar para um único ponto de limpeza de saída em uma função. Em funções realmente complexas, relaxamos essa regra para permitir outro salto adiante. Em ambos os casos, estamos evitando instruções profundamente aninhadas que geralmente ocorrem com a verificação de código de erro, o que ajuda na legibilidade e manutenção.
A discussão mais cuidadosa e completa das declarações goto, seus usos legítimos e construções alternativas que podem ser usadas no lugar de "declarações goto virtuosas", mas que podem ser abusadas tão facilmente quanto as declarações goto, é o artigo de Donald Knuth " Programação estruturada com declarações goto " , nas Pesquisas de Computação de dezembro de 1974 (volume 6, nº 4. pp. 261 - 301).
Não é de surpreender que alguns aspectos deste artigo de 39 anos sejam datados: aumentos de ordem de magnitude no poder de processamento tornam imperceptíveis algumas das melhorias de desempenho de Knuth para problemas de tamanho médio, e novas construções de linguagem de programação foram inventadas desde então. (Por exemplo, os blocos try-catch substituem o Construto de Zahn, embora raramente sejam usados dessa maneira.) Mas Knuth cobre todos os lados do argumento e deve ser uma leitura obrigatória antes que alguém refaça o problema novamente.
Em um módulo Perl, você ocasionalmente deseja criar sub-rotinas ou fechamentos em tempo real. O problema é que, depois de criar a sub-rotina, como você a acessa. Você pode simplesmente chamá-lo, mas se a sub caller()
- rotina usar , não será tão útil quanto poderia ser. É aí que a goto &subroutine
variação pode ser útil.
sub AUTOLOAD{
my($self) = @_;
my $name = $AUTOLOAD;
$name =~ s/.*:://;
*{$name} = my($sub) = sub{
# the body of the closure
}
goto $sub;
# nothing after the goto will ever be executed.
}
Você também pode usar esse formulário goto
para fornecer uma forma rudimentar de otimização de chamada de cauda.
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
$tally *= $n--;
@_ = ($n,$tally);
goto &factorial;
}
(Na versão 16 do Perl 5, seria melhor escrever como goto __SUB__;
)
Existe um módulo que importará um tail
modificador e outro que importará recur
se você não gostar de usar este formulário goto
.
use Sub::Call::Tail;
sub AUTOLOAD {
...
tail &$sub( @_ );
}
use Sub::Call::Recur;
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
recur( $n-1, $tally * $n );
}
goto
é melhor realizada com outras palavras-chave.Como redo
um pouco de código:
LABEL: ;
...
goto LABEL if $x;
{
...
redo if $x;
}
Ou vá para last
um pouco de código de vários lugares:
goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
last if $x;
...
last if $y
...
}
Se sim, por quê?
C não possui interrupção de vários níveis / rotulada e nem todos os fluxos de controle podem ser facilmente modelados com iteração e decisões primitivas de C. Os gotos contribuem bastante para corrigir essas falhas.
Às vezes, é mais claro usar algum tipo de variável de flag para efetuar um tipo de quebra de pseudo-nível múltiplo, mas nem sempre é superior ao goto (pelo menos um goto permite determinar facilmente para onde o controle vai, ao contrário de uma variável de flag ) e, às vezes, você simplesmente não deseja pagar o preço de desempenho dos sinalizadores / outras contorções para evitar o goto.
O libavcodec é um trecho de código sensível ao desempenho. A expressão direta do fluxo de controle é provavelmente uma prioridade, porque tenderá a funcionar melhor.
Da mesma forma, ninguém jamais implementou a declaração "COME FROM" ....
Acho o uso do {{while) (falso) totalmente revoltante. É concebível que possa me convencer de que é necessário em algum caso estranho, mas nunca que seja um código limpo e sensível.
Se você deve executar um loop desse tipo, por que não tornar explícita a dependência da variável flag?
for (stepfailed=0 ; ! stepfailed ; /*empty*/)
/*empty*/
ser stepfailed = 1
? De qualquer forma, como isso é melhor que um do{}while(0)
? Em ambos, você precisa break
sair dele (ou no seu stepfailed = 1; continue;
). Parece desnecessário para mim.
1) O uso mais comum de goto que eu conheço é emular manipulação de exceção em idiomas que não a oferecem, nomeadamente em C. (O código fornecido pela Nuclear acima é apenas isso.) Veja o código-fonte do Linux e você ' Verei um bazilhão de gotos usados dessa maneira; havia cerca de 100.000 gotos no código Linux, de acordo com uma pesquisa rápida realizada em 2013: http://blog.regehr.org/archives/894 . O uso do Goto é mencionado no guia de estilo de codificação do Linux: https://www.kernel.org/doc/Documentation/CodingStyle . Assim como a programação orientada a objetos é emulada usando estruturas preenchidas com ponteiros de função, o goto tem seu lugar na programação C. Então, quem está certo: Dijkstra ou Linus (e todos os codificadores de kernel do Linux)? É teoria versus prática basicamente.
No entanto, existe o problema comum de não ter suporte no nível do compilador e verificações de construções / padrões comuns: é mais fácil usá-los incorretamente e introduzir bugs sem verificações no tempo de compilação. O Windows e o Visual C ++, mas no modo C, oferecem tratamento de exceções via SEH / VEH por esse mesmo motivo: as exceções são úteis mesmo fora das linguagens OOP, ou seja, em uma linguagem processual. Mas o compilador nem sempre pode salvar seu bacon, mesmo que ofereça suporte sintático para exceções no idioma. Considere como exemplo do último caso o famoso bug "goto fail" do SSL da Apple, que apenas duplicou um goto com consequências desastrosas ( https://www.imperialviolet.org/2014/02/22/applebug.html ):
if (something())
goto fail;
goto fail; // copypasta bug
printf("Never reached\n");
fail:
// control jumps here
Você pode ter exatamente o mesmo bug usando exceções suportadas pelo compilador, por exemplo, em C ++:
struct Fail {};
try {
if (something())
throw Fail();
throw Fail(); // copypasta bug
printf("Never reached\n");
}
catch (Fail&) {
// control jumps here
}
Mas ambas as variantes do bug podem ser evitadas se o compilador analisá-lo e avisá-lo sobre código inacessível. Por exemplo, compilar com o Visual C ++ no nível de aviso / W4 localiza o erro nos dois casos. O Java, por exemplo, proíbe código inacessível (onde ele pode ser encontrado!) Por uma boa razão: é provável que seja um bug no código médio de Joe. Desde que a construção goto não permita alvos que o compilador não consegue descobrir facilmente, como gotos para endereços computados (**), não é mais difícil para o compilador encontrar código inacessível dentro de uma função com gotos do que usar o Dijkstra código aprovado.
(**) Nota de rodapé: Gotos para números de linhas calculados são possíveis em algumas versões do Basic, por exemplo, GOTO 10 * x, em que x é uma variável. De maneira confusa, em Fortran, "goto computado" refere-se a uma construção equivalente a uma instrução switch em C. O padrão C não permite gotos computados no idioma, mas apenas gotos para rótulos declarados estaticamente / sintaticamente. O GNU C, no entanto, possui uma extensão para obter o endereço de uma etiqueta (o operador unário, prefixo &&) e também permite ir para uma variável do tipo void *. Consulte https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html para obter mais informações sobre este subtópico obscuro. O restante deste post não se preocupa com esse recurso obscuro do GNU C.
Os gotos C padrão (ou seja, não computados) geralmente não são a razão pela qual o código inacessível não pode ser encontrado em tempo de compilação. O motivo usual é o código lógico como o seguinte. Dado
int computation1() {
return 1;
}
int computation2() {
return computation1();
}
É tão difícil para um compilador encontrar código inacessível em qualquer uma das três construções a seguir:
void tough1() {
if (computation1() != computation2())
printf("Unreachable\n");
}
void tough2() {
if (computation1() == computation2())
goto out;
printf("Unreachable\n");
out:;
}
struct Out{};
void tough3() {
try {
if (computation1() == computation2())
throw Out();
printf("Unreachable\n");
}
catch (Out&) {
}
}
(Desculpe meu estilo de codificação relacionado a chaves, mas tentei manter os exemplos o mais compactos possível.)
O Visual C ++ / W4 (mesmo com / Ox) falha ao encontrar código inacessível em qualquer um desses e, como você provavelmente sabe, o problema de encontrar código inacessível é indecidível em geral. (Se você não acredita em mim: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )
Como um problema relacionado, o C goto pode ser usado para emular exceções apenas dentro do corpo de uma função. A biblioteca C padrão oferece um par de funções setjmp () e longjmp () para emular saídas / exceções não locais, mas essas apresentam algumas desvantagens sérias em comparação com o que outros idiomas oferecem. O artigo da Wikipedia http://en.wikipedia.org/wiki/Setjmp.h explica bastante bem esse último problema. Esse par de funções também funciona no Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), mas dificilmente alguém os utiliza porque o SEH / VEH é superior. Mesmo no Unix, acho que setjmp e longjmp raramente são usados.
2) Acho que o segundo uso mais comum do goto em C é a implementação de interrupção em vários níveis ou a continuação em vários níveis, que também é um caso de uso bastante incontroverso. Lembre-se de que o Java não permite rotular goto, mas permite interromper o rótulo ou continuar o rótulo. De acordo com http://www.oracle.com/technetwork/java/simple-142616.html , esse é realmente o caso de uso mais comum de gotos em C (90% dizem eles), mas na minha experiência subjetiva, o código do sistema tende a usar gotos para tratamento de erros com mais frequência. Talvez no código científico ou onde o sistema operacional ofereça tratamento de exceção (Windows), as saídas em vários níveis sejam o caso de uso dominante. Eles realmente não fornecem detalhes sobre o contexto de sua pesquisa.
Editado para adicionar: verifica-se que esses dois padrões de uso são encontrados no livro C de Kernighan e Ritchie, por volta da página 60 (dependendo da edição). Outra coisa a ser observada é que ambos os casos de uso envolvem apenas gotos avançados. E acontece que a edição MISRA C 2012 (ao contrário da edição de 2004) agora permite gotos, desde que sejam apenas avançados.
Alguns dizem que não há razão para ir em C ++. Alguns dizem que em 99% dos casos existem melhores alternativas. Isso não é raciocínio, apenas impressões irracionais. Aqui está um exemplo sólido em que o goto leva a um código legal, algo como um loop do-while aprimorado:
int i;
PROMPT_INSERT_NUMBER:
std::cout << "insert number: ";
std::cin >> i;
if(std::cin.fail()) {
std::cin.clear();
std::cin.ignore(1000,'\n');
goto PROMPT_INSERT_NUMBER;
}
std::cout << "your number is " << i;
Compare com código livre de goto:
int i;
bool loop;
do {
loop = false;
std::cout << "insert number: ";
std::cin >> i;
if(std::cin.fail()) {
std::cin.clear();
std::cin.ignore(1000,'\n');
loop = true;
}
} while(loop);
std::cout << "your number is " << i;
Eu vejo essas diferenças:
{}
bloco aninhado é necessário (embora do {...} while
pareça mais familiar)loop
é necessária uma variável extra , usada em quatro locaisloop
loop
não contém dados, apenas controla o fluxo da execução, que é menos compreensível do que o rótulo simplesHá outro exemplo
void sort(int* array, int length) {
SORT:
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
goto SORT; // it is very easy to understand this code, right?
}
}
Agora vamos nos livrar do "mal", vá para:
void sort(int* array, int length) {
bool seemslegit;
do {
seemslegit = true;
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
seemslegit = false;
}
} while(!seemslegit);
}
Você vê que é o mesmo tipo de uso do goto, é um padrão bem estruturado e não é encaminhado para o goto, pois muitos promovem a única maneira recomendada. Certamente você deseja evitar código "inteligente" como este:
void sort(int* array, int length) {
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
i = -1; // it works, but WTF on the first glance
}
}
O ponto é que o goto pode ser facilmente mal utilizado, mas o próprio goto não é o culpado. Observe que o rótulo tem escopo de função em C ++, portanto, não polui o escopo global, como na montagem pura, na qual os loops sobrepostos têm seu lugar e são muito comuns - como no código a seguir para 8051, onde a exibição de 7 segmentos está conectada à P1. O programa faz um loop do segmento de raio em torno de:
; P1 states loops
; 11111110 <-
; 11111101 |
; 11111011 |
; 11110111 |
; 11101111 |
; 11011111 |
; |_________|
init_roll_state:
MOV P1,#11111110b
ACALL delay
next_roll_state:
MOV A,P1
RL A
MOV P1,A
ACALL delay
JNB P1.5, init_roll_state
SJMP next_roll_state
Há outra vantagem: o goto pode servir como loops, condições e outros fluxos nomeados:
if(valid) {
do { // while(loop)
// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket
} while(loop);
} // if(valid)
Ou você pode usar goto equivalente com recuo, para não precisar comentar se escolher o nome do rótulo com sabedoria:
if(!valid) goto NOTVALID;
LOOPBACK:
// more than one page of code here
if(loop) goto LOOPBACK;
NOTVALID:;
No Perl, use um rótulo para "ir" a partir de um loop - usando uma declaração "last", que é semelhante à quebra.
Isso permite um melhor controle sobre os loops aninhados.
O rótulo goto tradicional também é suportado, mas não tenho certeza de que existem muitos casos em que essa é a única maneira de conseguir o que você deseja - sub-rotinas e loops devem ser suficientes para a maioria dos casos.
goto &subroutine
. Que inicia a sub-rotina com o @_ atual, enquanto substitui a sub-rotina atual na pilha.
O problema com 'goto' e o argumento mais importante do movimento 'programação sem goto' é que, se você usá-lo com muita frequência, seu código, embora possa se comportar corretamente, torna-se ilegível, inalterável, irrevisível etc. Em 99,99% dos os casos 'goto' levam ao código do espaguete. Pessoalmente, não consigo pensar em nenhuma boa razão para usar 'goto'.
goto
). O uso de @ cschol é semelhante: Embora talvez não esteja criando um idioma no momento, ele está basicamente avaliando o esforço do designer.
goto
exceto em contextos em que traria variáveis à existência, é mais barato do que tentar suportar todo tipo de estrutura de controle que alguém possa precisar. Escrever código com goto
pode não ser tão bom quanto usar outra estrutura, mas poder escrevê- goto
lo ajudará a evitar "buracos na expressividade" - construções para as quais um idioma é incapaz de escrever código eficiente.
goto
no site de revisão de código, a eliminação da goto
simplifica bastante a lógica do código.
O GOTO pode ser usado, é claro, mas há uma coisa mais importante que o estilo do código, ou se o código é ou não legível que você deve ter em mente ao usá-lo: o código interno pode não ser tão robusto quanto você pense .
Por exemplo, observe os dois trechos de código a seguir:
If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)
Um código equivalente ao GOTO
If A == 0 Then GOTO FINAL EndIf
A = 0
FINAL:
Write("Value of A:" + A)
A primeira coisa que pensamos é que o resultado de ambos os bits de código será o "Valor de A: 0" (supomos uma execução sem paralelismo, é claro)
Isso não está correto: no primeiro exemplo, A sempre será 0, mas no segundo exemplo (com a instrução GOTO) A pode não ser 0. Por que?
O motivo é que, a partir de outro ponto do programa, posso inserir a GOTO FINAL
sem controlar o valor de A.
Este exemplo é muito óbvio, mas à medida que os programas ficam mais complicados, a dificuldade de ver esse tipo de coisa aumenta.
Material relacionado pode ser encontrado no famoso artigo do Sr. Dijkstra "Um caso contra a declaração GO TO"
Uso goto no seguinte caso: quando necessário, para retornar de funções em locais diferentes, e antes de retornar, é necessário fazer alguma inicialização:
versão não-goto:
int doSomething (struct my_complicated_stuff *ctx)
{
db_conn *conn;
RSA *key;
char *temp_data;
conn = db_connect();
if (ctx->smth->needs_alloc) {
temp_data=malloc(ctx->some_size);
if (!temp_data) {
db_disconnect(conn);
return -1;
}
}
...
if (!ctx->smth->needs_to_be_processed) {
free(temp_data);
db_disconnect(conn);
return -2;
}
pthread_mutex_lock(ctx->mutex);
if (ctx->some_other_thing->error) {
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -3;
}
...
key=rsa_load_key(....);
...
if (ctx->something_else->error) {
rsa_free(key);
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -4;
}
if (ctx->something_else->additional_check) {
rsa_free(key);
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -5;
}
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return 0;
}
Ir para a versão:
int doSomething_goto (struct my_complicated_stuff *ctx)
{
int ret=0;
db_conn *conn;
RSA *key;
char *temp_data;
conn = db_connect();
if (ctx->smth->needs_alloc) {
temp_data=malloc(ctx->some_size);
if (!temp_data) {
ret=-1;
goto exit_db;
}
}
...
if (!ctx->smth->needs_to_be_processed) {
ret=-2;
goto exit_freetmp;
}
pthread_mutex_lock(ctx->mutex);
if (ctx->some_other_thing->error) {
ret=-3;
goto exit;
}
...
key=rsa_load_key(....);
...
if (ctx->something_else->error) {
ret=-4;
goto exit_freekey;
}
if (ctx->something_else->additional_check) {
ret=-5;
goto exit_freekey;
}
exit_freekey:
rsa_free(key);
exit:
pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
free(temp_data);
exit_db:
db_disconnect(conn);
return ret;
}
A segunda versão facilita, quando você precisa alterar algo nas instruções de desalocação (cada uma é usada uma vez no código) e reduz a chance de ignorar qualquer uma delas ao adicionar uma nova ramificação. Movê-los em uma função não ajudará aqui, porque a desalocação pode ser feita em diferentes "níveis".
finally
blocos em c # #
finally
). Como alternativa, use goto
s, mas para um ponto de saída comum , que sempre faz toda a limpeza. Mas cada método de limpeza pode lidar com um valor nulo ou já limpo ou protegido por um teste condicional, sendo ignorado quando não apropriado.
goto
s que todos vão para o mesmo ponto de saída, que possui a mesma lógica (que requer um extra se por recurso, como você diz). Mas não se preocupe, quando C
você estiver certo, seja qual for o motivo do código em C, é quase certamente uma troca que favorece o código mais "direto". (Minha sugestão alças situações complexas onde pode ou não tenham sido atribuídas qualquer recurso dado, mas sim, um exagero neste caso..)
Edsger Dijkstra, um cientista da computação que teve grandes contribuições no campo, também era famoso por criticar o uso do GoTo. Há um pequeno artigo sobre seu argumento na Wikipedia .
É útil para o processamento de cadeias de caracteres de tempos em tempos.
Imagine algo como este exemplo printf-esque:
for cur_char, next_char in sliding_window(input_string) {
if cur_char == '%' {
if next_char == '%' {
cur_char_index += 1
goto handle_literal
}
# Some additional logic
if chars_should_be_handled_literally() {
goto handle_literal
}
# Handle the format
}
# some other control characters
else {
handle_literal:
# Complicated logic here
# Maybe it's writing to an array for some OpenGL calls later or something,
# all while modifying a bunch of local variables declared outside the loop
}
}
Você pode refatorar isso goto handle_literal
para uma chamada de função, mas se estiver modificando várias variáveis locais diferentes, será necessário passar referências a cada uma, a menos que seu idioma suporte fechamentos mutáveis. Você ainda teria que usar uma continue
declaração (que é indiscutivelmente uma forma de ir) após a chamada para obter a mesma semântica se sua lógica fizer com que um outro caso não funcione.
Também usei gotos criteriosamente em lexers, normalmente para casos semelhantes. Você não precisa deles na maioria das vezes, mas é bom ter esses casos estranhos.