Pergunta

Há muito tempo tenho a impressão de que goto nunca deve ser usado, se possível.Outro dia, enquanto examinava o libavcodec (que está escrito em C), notei vários usos dele.É sempre vantajoso usar goto em uma linguagem que suporta loops e funções?Se sim, por quê?

Foi útil?

Solução

Existem alguns motivos para usar a instrução "goto" que eu conheço (alguns já falaram sobre isso):

Sair de uma função de forma limpa

Freqüentemente, 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 código de limpeza em cada “ponto de saída” da função.

Saindo de loops aninhados

Se você estiver em um loop aninhado e precisar sair dele todos loops, um goto pode tornar isso muito mais limpo e simples do que instruções break e verificações if.

Melhorias de desempenho de baixo nível

Isso só é válido em código de desempenho crítico, mas as instruções goto são executadas muito rapidamente e podem lhe dar um impulso ao passar por uma função.No entanto, esta é uma faca de dois gumes, porque um compilador normalmente não consegue otimizar o código que contém gotos.

Observe que em todos esses exemplos, gotos estão restritos ao escopo de uma única função.

Outras dicas

Todo mundo que é anti-goto cita, direta ou indiretamente, o livro de Edsger Dijkstra GoTo considerado prejudicial artigo para fundamentar sua posição.Pena que o artigo de Dijkstra tenha virtualmente nada a ver com o caminho 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-less meme beira agora uma religião, até às suas escrituras ditadas do alto, aos seus sumos sacerdotes e à rejeição (ou pior) de supostos hereges.

Vamos contextualizar o artigo de Dijkstra para lançar um pouco de luz sobre o assunto.

Quando Dijkstra escreveu seu artigo, as linguagens populares da época eram linguagens procedurais não estruturadas, como BASIC, FORTRAN (os primeiros dialetos) e várias linguagens assembly.Era bastante comum que as pessoas que usavam linguagens de nível superior saltassem em toda a sua base de código em linhas de execução retorcidas e contorcidas 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.

ESSE é "o uso desenfreado da declaração vá para" contra o qual Dijkstra criticou em seu artigo de 1968. ESSE foi o ambiente em que viveu que o levou a escrever aquele artigo.A capacidade de pular para qualquer lugar que você quiser em seu código, em qualquer ponto que você quiser, era o que ele estava criticando e exigindo que fosse interrompido.Comparando isso com os poderes anêmicos de goto em C ou em outras linguagens mais modernas é simplesmente risível.

Já posso ouvir os cantos elevados dos cultistas enquanto eles enfrentam o herege."Mas", eles gritarão, "você pode tornar o código muito difícil de ler com goto em C." Ah, é?Você pode tornar o código muito difícil de ler sem goto também.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 é um 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*/

Não goto lá também.Deve, portanto, ser legível.

Qual é o meu ponto com esses exemplos?Não são os recursos da linguagem que tornam o código ilegível e impossível de manter.Não é a sintaxe que faz isso.São os maus programadores que causam isso.E maus programadores, como você pode ver no item acima, podem fazer qualquer recurso de linguagem ilegível e inutilizável.Como o for voltas lá em cima.(Você pode vê-los, certo?)

Agora, para ser justo, algumas construções linguísticas são mais fáceis de abusar do que outras.Se você for um programador C, entretanto, eu observaria com muito mais atenção cerca de 50% dos usos de #define muito antes de eu partir em uma cruzada contra goto!

Portanto, para aqueles que se deram ao trabalho de ler até aqui, há vários pontos-chave a serem observados.

  1. O artigo de Dijkstra sobre goto declarações foram escritas para um ambiente de programação onde goto era um muitomais potencialmente prejudicial do que na maioria das linguagens modernas que não são montadoras.
  2. Descartando automaticamente todos os usos de goto Por causa disso, é tão racional quanto dizer "tentei me divertir uma vez, mas não gostei, então agora sou contra".
  3. Existem usos legítimos do moderno (anêmico) goto Declarações no código que não podem ser substituídas adequadamente por outras construções.
  4. É claro que existem usos ilegítimos das mesmas declarações.
  5. Existem também usos ilegítimos das declarações de controle modernas, como o "godo"abominação onde um sempre falso do loop é quebrado pelo uso break no lugar de um goto.Estas são muitas vezes piores do que o uso criterioso de goto.

Obedecer cegamente às melhores práticas não é uma boa prática.A ideia de evitar goto instruções como a principal forma de controle de fluxo é evitar a produção de código espaguete ilegível.Se usados ​​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 utiliza com frequência, mas criteriosamente.Mesmo com o goto declarações, seu código ainda é perfeitamente legível.

Conclusão:Evitando goto para evitar goto é inútil.O que você realmente deseja evitar é produzir código ilegível.Se seu goto-o código carregado é legível, então não há nada de errado com ele.

Desde goto dificulta o raciocínio sobre o fluxo do programa1 (também conhecido como.“código espaguete”), goto geralmente é usado apenas para compensar recursos ausentes:O uso de goto pode realmente ser aceitável, mas apenas se a linguagem 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 é adequado para avançar para um único ponto de limpeza de saída em uma função.

Isso é verdade – mas somente se a linguagem não permitir o tratamento estruturado de exceções com código de limpeza (como RAII ou finally), que faz melhor o mesmo trabalho (já que foi especialmente desenvolvido para isso), ou quando há um bom motivo para não empregar tratamento estruturado de exceções (mas você nunca terá esse caso, exceto em um nível muito baixo).

Na maioria das outras línguas, o único uso aceitável de goto é sair dos loops aninhados.E mesmo aí é quase sempre melhor elevar o loop externo para um método próprio e usar return em vez de.

Fora isso, goto é um sinal de que não houve reflexão suficiente sobre o trecho específico do código.


1 Linguagens modernas que suportam goto implementar algumas restrições (por ex. goto pode não entrar ou sair das funções), mas o problema permanece fundamentalmente o mesmo.

Aliás, o mesmo também se aplica a outros recursos da linguagem, principalmente exceções.E geralmente existem regras rígidas para usar esses recursos apenas onde indicado, como a regra de não usar exceções para controlar o fluxo de programa não excepcional.

Bem, há uma coisa que é sempre pior do que goto's;uso estranho de outros operadores de fluxo de programa 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

Em C# trocar declaração não permite quedas.Então Vá para é usado para transferir o controle para uma etiqueta específica da caixa do interruptor ou para o padrão rótulo.

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 de “não haver falhas”.Fall-through é permitido se uma instrução case não tiver código.

#ifdef TONGUE_IN_CHEEK

Perl tem um goto que permite implementar chamadas de cauda dos pobres.:-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 para limpezas ou para implementar Dispositivo de Duff, ou semelhante.É tudo uma questão de usar, não de 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çar uma exceção apenas para escapar de uma estrutura de controle profundamente aninhada, sob circunstâncias completamente não excepcionais.)

Escrevi mais do que algumas linhas de linguagem assembly ao longo dos anos.Em última análise, toda linguagem de alto nível é compilada em gotos.Ok, chame-os de “galhos” ou “saltos” ou qualquer outra coisa, mas eles são gotos.Alguém pode escrever assembler goto-less?

Agora, claro, você pode apontar para um programador Fortran, C ou BASIC que fazer confusão com gotos é uma receita de espaguete à bolonhesa.A resposta, porém, não é evitá-los, mas sim usá-los com cuidado.

Uma faca pode ser usada para preparar comida, libertar alguém ou matar alguém.Passamos sem facas por medo destas?Da mesma forma, o goto:usado descuidadamente atrapalha, usado com cuidado ajuda.

Dê uma olhada Quando usar Goto ao programar em C:

Embora o uso de goto seja quase sempre uma má prática de programação (certamente você pode encontrar uma maneira melhor de fazer 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á razão sólida para usar goto 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 resto da lógica do seu programa e quiser evitar reescrever o código de limpeza várias vezes em todo o seu código, então goto pode ser uma boa escolha.

O que 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 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, então 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, será necessário alterar sua função de limpeza novamente;e então você precisará alterar os argumentos dessa função.

Usando 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 seguinte tem acesso a tudo o que será necessário para realizar a limpeza e você conseguiu reduzir consideravelmente o número de pontos de mudança.Outro benefício é que você deixou de ter vários pontos de saída para sua função e passou a ter apenas um;não há chance de você retornar acidentalmente da função sem limpar.

Além disso, como 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, goto realmente ajuda a escrever um código mais estruturado.


Em um mundo, goto deve sempre ser usado com moderação e como último recurso - mas há hora e lugar para isso.A pergunta não deveria ser "você precisa usá-lo", mas "é a melhor escolha" para usá-lo.

Uma das razões pelas quais goto é ruim, além do estilo de codificação, é que você pode usá-lo para criar sobreposição, mas não aninhado rotações:

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

Isso criaria a estrutura de fluxo de controle bizarra, mas possivelmente legal, onde uma sequência como (a, b, c, b, a, b, a, b, ...) é possível, o que deixa os hackers do compilador infelizes.Aparentemente, existem vários truques inteligentes de otimização que dependem da não ocorrência desse tipo de estrutura.(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 sejam feitas para o código que contém gotoS.

Pode ser útil se você saber apenas, "ah, a propósito", convence o compilador a emitir código mais rápido.Pessoalmente, prefiro tentar explicar ao compilador o que é provável e o que não é antes de usar um truque como goto, mas, sem dúvida, também posso tentar goto antes de hackear o assembler.

Acho engraçado que algumas pessoas cheguem ao ponto de fornecer uma lista de casos em que goto é aceitável, dizendo que todos os outros usos são inaceitáveis.Você realmente acha que conhece todos os casos em que goto é a melhor escolha para expressar um algoritmo?

Para ilustrar, vou dar um exemplo que ninguém aqui mostrou ainda:

Hoje eu estava escrevendo um código para inserir um elemento em uma tabela hash.A tabela hash é um cache de cálculos anteriores que pode ser sobrescrito à vontade (afetando o desempenho, mas não a correção).

Cada balde da tabela hash tem 4 slots, e eu tenho vários critérios para decidir qual elemento substituir quando um balde estiver cheio.No momento, isso significa fazer até três passagens por 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

Pareceria cada vez pior se mais passagens fossem adicionadas, enquanto a versão com goto mantém o mesmo nível de indentação em todos os momentos e evita o uso de instruções if falsas cujo resultado está implícito na execução do loop anterior.

Portanto, há outro caso em que goto torna o código mais limpo e fácil de escrever e entender...Tenho certeza de que existem muitos mais, então não finja conhecer todos os casos em que goto é útil, menosprezando os bons que você não conseguiu imaginar.

A regra com goto que usamos é que goto é adequado 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 outros avanços.Em ambos os casos, evitamos instruções if profundamente aninhadas que ocorrem frequentemente com a verificação de código de erro, o que ajuda na legibilidade e na manutenção.

A discussão mais cuidadosa e completa sobre 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 declarações goto, é o artigo de Donald Knuth "Programação estruturada com instruções goto", nas Pesquisas de Computação de dezembro de 1974 (volume 6, no.4.pp.261-301).

Não é de surpreender que alguns aspectos deste artigo de 39 anos estejam desatualizados:Aumentos de ordens de magnitude no poder de processamento tornam algumas das melhorias de desempenho de Knuth imperceptíveis para problemas de tamanho moderado, e novas construções de linguagem de programação foram inventadas desde então.(Por exemplo, os blocos try-catch incluem o Construct de Zahn, embora raramente sejam usados ​​dessa forma.) Mas Knuth cobre todos os lados do argumento e deve ser uma leitura obrigatória antes que alguém refaça o assunto novamente.

Em um módulo Perl, ocasionalmente você deseja criar sub-rotinas ou encerramentos dinamicamente.O problema é que, depois de criar a sub-rotina, como você chega a ela.Você poderia simplesmente chamá-lo, mas se a sub-rotina usar caller() não será tão útil quanto poderia ser.É aí que o goto &subroutine a variação pode ser útil.

Aqui está um exemplo rápido:

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 esta forma de goto para fornecer uma forma rudimentar de otimização de chamada final.

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

( Em Perl 5 versão 16 isso seria melhor escrito como goto __SUB__; )

Existe um módulo que irá importar um tail modificador e um que irá importar recur se você não gosta de usar esta forma de 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 );
}

A maioria das outras razões para usar goto são melhor feitos com outras palavras-chave.

Como redousando um pouco de código:

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

Ou indo para o last de 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 tem quebra multinível/rotulada e nem todos os fluxos de controle podem ser facilmente modelados com iteração e primitivas de decisão de C.gotos percorrem um longo caminho para corrigir essas falhas.

Às vezes é mais claro usar algum tipo de variável de flag para efetuar uma espécie de quebra de pseudo-multinível, mas nem sempre é superior ao goto (pelo menos um goto permite determinar facilmente para onde vai o controle, ao contrário de uma variável de flag ), e às vezes você simplesmente não quer pagar o preço de desempenho das bandeiras/outras contorções para evitar o goto.

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.

Ainda bem que ninguém nunca implementou a declaração "COME FROM"...

Acho o uso do{} while(false) totalmente revoltante.É concebível que possa me convencer de que é necessário em algum caso estranho, mas nunca que se trata de um código limpo e sensato.

Se você precisa fazer algum loop desse tipo, por que não tornar explícita a dependência da variável flag?

for (stepfailed=0 ; ! stepfailed ; /*empty*/)

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ê pensa.

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 com 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á aquele “Valor de A:0" (supomos uma execução sem paralelismo, claro)

Isso não está correto:na primeira amostra, A será sempre 0, mas na segunda amostra (com a instrução GOTO) A pode não ser 0.Por que?

A razão é porque de outro ponto do programa posso inserir um 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 coisas aumenta.

Material relacionado pode ser encontrado no famoso artigo do Sr.Dijkstra "Um caso contra a declaração GO TO"

1) O uso mais comum de goto que conheço é emular o tratamento de exceções em linguagens que não o oferecem, nomeadamente em C.(O código fornecido pela Nuclear acima é apenas isso.) Observe o código-fonte do Linux e você verá um zilhão de gotos usados ​​dessa maneira;havia cerca de 100.000 gotos no código Linux, de acordo com uma rápida pesquisa realizada em 2013: http://blog.regehr.org/archives/894.O uso do Goto é até 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, goto tem seu lugar na programação C.Então, quem está certo:Dijkstra ou Linus (e todos os codificadores de kernel Linux)?É teoria vs.praticar basicamente.

No entanto, existe o problema usual de não ter suporte no nível do compilador e verificações de construções/padrões comuns:é mais fácil usá-los de maneira errada e introduzir bugs sem verificações em tempo de compilação.Windows e Visual C++, mas no modo C, oferecem tratamento de exceções via SEH/VEH por este motivo:exceções são úteis mesmo fora de linguagens OOP, ou seja,em uma linguagem processual.Mas o compilador nem sempre consegue salvar seu bacon, mesmo que ofereça suporte sintático para exceções na linguagem.Consideremos como exemplo deste ú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 analisar e avisar sobre código inacessível.Por exemplo, compilar com Visual C++ no nível de aviso /W4 encontra o bug em ambos os casos.Java, por exemplo, proíbe código inacessível (onde pode encontrá-lo!) por um bom motivo:é provável que seja um bug no código médio de Joe.Contanto que a construção goto não permita alvos que o compilador não possa descobrir facilmente, como gotos para endereços computados(**), não será mais difícil para o compilador encontrar código inacessível dentro de uma função com gotos do que usando Dijkstra -código aprovado.

(**) Nota de rodapé:Gotos para números de linha computados são possíveis em algumas versões do Basic, por ex.GOTO 10*x onde x é uma variável.Um tanto confuso, em Fortran "goto computado" refere-se a uma construção que é equivalente a uma instrução switch em C.O padrão C não permite gotos computados na linguagem, mas apenas gotos para rótulos declarados estaticamente/sintaticamente.GNU C, entretanto, possui uma extensão para obter o endereço de um rótulo (o operador unário, prefixo &&) e também permite ir para uma variável do tipo void*.Ver https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html para saber mais sobre este subtópico obscuro.O restante deste post não se preocupa com esse recurso obscuro do GNU C.

Padrão C (ou seja,não computado) gotos 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 é um código lógico como o seguinte.Dado

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

É igualmente 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.)

Visual C++ /W4 (mesmo com /Ox) não consegue encontrar código inacessível em nenhum deles e, como você provavelmente sabe, o problema de encontrar código inacessível é indecidível em geral.(Se você não acredita em mim sobre isso: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf)

Como questão relacionada, 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 elas têm algumas desvantagens sérias em comparação com o que outras linguagens oferecem.O artigo da Wikipédia http://en.wikipedia.org/wiki/Setjmp.h explica bastante bem esta última questão.Este par de funções também funciona no Windows (http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx), mas quase ninguém os usa lá porque SEH/VEH é superior.Mesmo no Unix, acho que setjmp e longjmp raramente são usados.

2) Acho que o segundo uso mais comum de goto em C é a implementação de interrupção multinível ou continuação multinível, que também é um caso de uso bastante incontroverso.Lembre-se de que Java não permite goto label, mas permite break label ou continue label.De acordo com http://www.oracle.com/technetwork/java/simple-142616.html, este é na verdade o caso de uso mais comum de gotos em C (90% dizem), mas na minha experiência subjetiva, o código do sistema tende a usar gotos para tratamento de erros com mais frequência.Talvez em código científico ou onde o sistema operacional oferece tratamento de exceções (Windows), as saídas de 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:Acontece 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 digna de nota é que ambos os casos de uso envolvem apenas gotos de encaminhamento.E acontece que a edição MISRA C 2012 (ao contrário da edição 2004) agora permite gotos, desde que sejam apenas avançados.

Em Perl, uso de um rótulo para "goto" de um loop - usando uma instrução "last", que é semelhante a break.

Isso permite melhor controle sobre loops aninhados.

O goto tradicional rótulo também é compatível, mas não tenho certeza se há muitos casos em que essa seja a única maneira de conseguir o que você deseja - sub-rotinas e loops devem ser suficientes na maioria dos casos.

O problema com 'goto' e o argumento mais importante do movimento 'goto-less programming' é que, se você usá-lo com muita frequência, seu código, embora possa se comportar corretamente, torna-se ilegível, insustentável, irrevisável, etc.Em 99,99% dos casos, 'goto' leva ao código espaguete.Pessoalmente, não consigo pensar em nenhuma boa razão para usar 'goto'.

Edsger Dijkstra, um cientista da computação que teve importantes contribuições na área, também ficou famoso por criticar o uso do GoTo.Há um pequeno artigo sobre seu argumento sobre Wikipédia.

Eu uso goto no seguinte caso:quando necessário retornar de funções em locais diferentes, e antes do retorno, alguma reinicialização precisa ser feita:

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 pular qualquer uma delas ao adicionar uma nova ramificação.Movê-los em uma função não vai ajudar aqui, pois a desalocação pode ser feita em diferentes “níveis”.

Alguns dizem que não há razão para ir para C++.Alguns dizem que em 99% dos casos existem alternativas melhores. Isto não é raciocínio, apenas impressões irracionais. Aqui está um exemplo sólido onde 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-o com o código goto-free:

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:

  • aninhado {} bloco é necessário (embora do {...} while parece mais familiar)
  • extra loop variável é necessária, usada em quatro lugares
  • leva mais tempo para ler e compreender o trabalho com o loop
  • o loop não contém nenhum dado, apenas controla o fluxo de execução, que é menos compreensível que um simples rótulo

Há 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" goto:

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 de goto, é um padrão bem estruturado e não é tão avançado que muitos promovem como a única forma recomendada.Certamente você deseja evitar códigos "inteligentes" 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
  }
}

A questão é que goto pode ser facilmente mal utilizado, mas goto em si não é o culpado.Observe que o rótulo tem escopo de função em C++, portanto não polui o escopo global como no assembly puro, no qual loops sobrepostos têm seu lugar e são muito comuns - como no código a seguir para 8051, onde o display de 7 segmentos está conectado a P1.O programa faz um loop no segmento relâmpago:

; 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:goto pode servir como loops nomeados, condições e outros fluxos:

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 de comentários 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:;
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top