É sempre vantajoso usar 'goto' em uma linguagem que suporta loops e funções? Se sim, por quê?


201

Há muito tempo tenho a impressão de que gotonunca deve ser usada, se possível. Enquanto examinava o libavcodec (que está escrito em C) outro dia, notei vários usos. É sempre vantajoso usar gotoem uma linguagem que suporte loops e funções? Se sim, por quê?

Respostas:


242

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.


15
A maneira correta de sair de loops aninhados é refatorar o loop interno em um método separado.
Jason

129
@Jason - Bah. Isso é um monte de besteira. Substituir gotopor returné simplesmente bobo. Não é "refatorar" nada, é apenas "renomear" para que as pessoas que cresceram em um gotoambiente 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 é apenas uma ferramenta , do que ver alguém movendo o loop em algum lugar não relacionado apenas para evitar a goto.
Chris Lutz

18
Existem locais onde os gotos são importantes: por exemplo, um ambiente C ++ sem exceção. Na fonte do Silverlight, temos dezenas de milhares (ou mais) de instruções goto para funções seguras, através do uso de macros - codecs e bibliotecas de mídia principais geralmente trabalham com valores de retorno e nunca com exceções, e é difícil combinar esses mecanismos de tratamento de erros. uma única maneira de desempenho.
Jeff Wilcox

79
É importante notar que todos break, continue, returnsão, basicamente goto, apenas em embalagem agradável.
el.pescado

16
Não vejo como do{....}while(0)deve ser uma idéia melhor do que ir, exceto pelo fato de funcionar em Java.
Lista Jeremy

906

Todo mundo que é anti- gotocita, 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 gotodeclaraçõ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 gotomeme-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 gotoC 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 gotoem 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*/

gotoTambé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 forlaç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 #definemuito 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.

  1. O artigo de Dijkstra sobre gotodeclarações foi escrito para um ambiente de programação em que gotoera muito mais potencialmente prejudicial do que na maioria das linguagens modernas que não são montadoras.
  2. Descartar automaticamente todos os usos gotodevido a isso é tão racional quanto dizer "Tentei me divertir uma vez, mas não gostei, então agora sou contra".
  3. Existem usos legítimos das gotoinstruções modernas (anêmicas) no código que não podem ser adequadamente substituídos por outras construções.
  4. É claro que existem usos ilegítimos das mesmas declarações.
  5. Também existem usos ilegítimos das declarações de controle modernas, como a " godo" abominação em que um doloop sempre falso é interrompido breakno lugar de a goto. Estes são frequentemente piores do que o uso criterioso de goto.

42
@pocjoc: Escrevendo otimização recursiva de cauda em C, por exemplo. Isso ocorre na implementação de intérpretes / durações de linguagem funcional, mas é representativo de uma categoria interessante de problemas. Muitos compiladores escritos em C usam goto por esse motivo. É um uso de nicho que não ocorre na maioria das situações, é claro, mas nas situações em que é chamado, faz muito sentido usar.
Zxq9

66
Por que todo mundo fala sobre o artigo de Dijkstra, "Vá para o considerado prejudicial", sem mencionar a resposta de Knuth, "Programação estruturada com ir para declarações"?
Oblomov

22
@ Nietzche-jou "este é um homem de palha bastante flagrante" [...] "Ele está sarcasticamente usando uma dicotomia falsa para mostrar por que o estigma é ilógico. Um Strawman é uma falácia lógica na qual você intencionalmente deturpa a posição do oponente para fazer parecer que a posição deles é facilmente derrotada. Por exemplo, "ateus são ateus porque odeiam a Deus". Uma falsa dicotomia é quando você assume que o extremo oposto é verdadeiro quando existe pelo menos uma possibilidade adicional (ou seja, preto e branco). Por exemplo, "Deus odeia homossexuais, portanto, ele ama heteros".
Braden Best

27
@JLRishe Você está lendo muito fundo em algo que nem está lá. É apenas uma verdadeira falácia lógica se a pessoa que a usou honestamente acreditar que faz sentido. Mas ele disse sarcasticamente, indicando claramente que ele sabe que é ridículo, e é assim que ele está mostrando. Ele não está "usando para justificar seu argumento"; ele está usando isso para mostrar por que é ridículo dizer que os gotos transformam código automaticamente em espaguete. Ou seja, você ainda pode escrever código de merda sem gotos. Esse é o ponto dele. E a falsa dicotomia sarcástica é seu método de ilustrá-la de maneira bem-humorada.
Braden Best

32
-1 para fazer um grande argumento porque a observação de Dijkstra não é aplicável, enquanto esquecendo-se de mostrar o que as vantagens de gotorealmente são (que é a pergunta postada)
Maarten Bodewes

154

Obedecer às melhores práticas cegamente não é uma prática recomendada. A idéia de evitar gotodeclaraçõ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 gotodeclarações, seu código ainda é perfeitamente legível.

Conclusão: Evitar gotoevitar gotoé inútil. O que você realmente deseja evitar é produzir código ilegível. Se o seu gotocódigo -laden for legível, não há nada errado com ele.


36

Como gotodificulta o raciocínio sobre o fluxo do programa 1 (também conhecido como “código de espaguete”), gotogeralmente é usado para compensar os recursos ausentes: o uso de gotopode 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 gotoimplementam algumas restrições (por exemplo, gotopodem 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.


4
Apenas curioso aqui, mas e o caso de usar gotos para limpar o código. Com a limpeza, quero dizer, não apenas a desalocação da memória, mas também, digamos, o log de erros. Eu estava lendo várias postagens e, aparentemente, ninguém escreve código que imprime logs .. hmm ?!
Shiva

37
"Basicamente, devido à natureza defeituosa do Goto (e acredito que isso é incontroverso)" -1 e parei de ler lá.
o0 '.

14
A advertência contra o goto vem da era da programação estruturada, onde os retornos iniciais também eram considerados maus. Mover loops aninhados para uma função troca um "mal" por outro e cria uma função que não tem nenhuma razão real para existir (fora de "isso me permitiu evitar o uso de um goto!"). É verdade que o goto é a ferramenta mais poderosa para produzir código de espaguete, se usado sem restrições, mas esse é um aplicativo perfeito; simplifica o código. Knuth defendeu esse uso, e Dijkstra disse: "Não caia na armadilha de acreditar que sou terrivelmente dogmático em relação a ir".
Mud

5
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.
Christian

3
@Ud: nos casos que encontro (diariamente), a criação de uma função "que não tem nenhum motivo real para existir" é a melhor solução. Razão: remove os detalhes da função de nível superior, para que o resultado tenha um fluxo de controle de fácil leitura. Pessoalmente, não encontro situações em que um goto produz o código mais legível. Por outro lado, eu uso vários retornos, em funções simples, onde outra pessoa pode preferir ir a um único ponto de saída. Eu adoraria ver contra-exemplos onde ir para goto é mais legível do que refatorar para funções bem nomeadas.
Página Inicial>

35

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

2
do{}while(false)Eu acho que pode ser considerado idiomático. Você não tem permissão para discordar: D
Thomas Eding

37
@ trinithis: Se é "idiomático", isso é apenas por causa do culto anti-goto. Se você olhar atentamente, perceberá que é apenas uma maneira de dizer 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.
cHao 9/12/11

5
@ThomasEding Eding Há uma exceção ao seu ponto. Se você já fez alguma programação C / C ++ e teve que usar #define, você saberia que {} while (0) é um dos padrões para encapsular várias linhas de código. Por exemplo: #define do {memcpy (a, b, 1); algo ++;} enquanto (0) é mais seguro e melhor que #define memcpy (a, b, 1); algo ++
Ignas2526

10
@ Ignas2526 Você acabou de mostrar muito bem como #definesão muitas, muitas vezes muito piores do que usar de gotovez em quando: D
Luaan

3
+1 para uma boa lista de maneiras pelas quais as pessoas tentam evitar gotos, ao extremo; Já vi menções de tais técnicas em outros lugares, mas esta é uma lista excelente e sucinta. Ponderando por que, nos últimos 15 anos, minha equipe nunca precisou de nenhuma dessas técnicas, nem usou gotos. Mesmo em um aplicativo cliente / servidor com centenas de milhares de linhas de código, por vários programadores. C #, java, PHP, python, javascript. Código do cliente, servidor e aplicativo. Não se gabar, nem proselitismo para uma posição, apenas verdadeiramente curioso por que algumas pessoas encontrar situações que implorar para gotos como solução mais clara, e outros não ...
ToolmakerSteve

28

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.


3
Interruptor cair por ele é suportado em .NET 2.0 - msdn.microsoft.com/en-us/library/06tc147t(VS.80).aspx
rjzii

9
Somente se o caso não tiver um corpo de código. Se houver código, você deverá usar a palavra-chave goto.
Matthew Whited

26
Essa resposta é muito engraçada - o C # removeu o fall-through porque muitos o veem como prejudicial, e este exemplo usa goto (também visto como prejudicial por muitos) para reviver o comportamento original, supostamente prejudicial, MAS o resultado geral é realmente menos prejudicial ( porque o código deixa claro que a descoberta é intencional!).
thomasrutter

9
Só porque uma palavra-chave é escrita com as letras GOTO não a torna ir. O caso apresentado não é do tipo goto. É uma construção de instrução switch para avanço. Então, novamente, eu realmente não conheço C # muito bem, então posso estar errado.
Thomas Eding

1
Bem, nesse caso, é um pouco mais do que indireto (porque você pode dizer goto case 5:quando está no caso 1). Parece que a resposta de Konrad Rudolph está correta aqui: gotoestá 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 continuesolicitá-la explicitamente.
18720 David Stone

14

#ifdef TONGUE_IN_CHEEK

O Perl tem um gotoque 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 gotode 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 longjmpexceções call/cce 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 acho que esse é o único motivo para usar goto no Perl.
Brad Gilbert

12

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.


1
Talvez você quiser ler por isso que eu acredito que este é fundamentalmente errado no stackoverflow.com/questions/46586/...
Konrad Rudolph

15
Qualquer um que avance o "tudo compila para o JMP de qualquer maneira!" O argumento basicamente não entende o ponto por trás da programação em uma linguagem de nível superior.
Nietzche-jou

7
Tudo o que você realmente precisa é subtrair e ramificar. Tudo o resto é por conveniência ou desempenho.
18720 David Stone

8

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, gotosempre 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.


7

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 gotos.

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 gotoantes de hackear o assembler.


3
Bem ... me leva de volta aos dias de programação do FORTRAN em uma empresa de informações financeiras. Em 2007.
Marcin

5
Qualquer estrutura de linguagem pode ser abusada de maneira a torná-la ilegível ou com baixo desempenho.
APENAS MINHA OPINIÃO correta

@ APENAS: O ponto não é sobre legibilidade ou desempenho ruim, mas suposições e garantias sobre o gráfico do fluxo de controle. Qualquer abuso de goto seria para aumentar o desempenho (ou legibilidade).
Anders Eurenius

3
Eu argumentaria que uma das razões pelas quais 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.
cHao 6/01/14

1
... uma tradução direta do algoritmo necessário para o código baseado no GOTO pode ser muito mais fácil de validar do que uma confusão de variáveis ​​de flag e construções de loop abusadas.
Supercat

7

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.


1
No exemplo que você deu, eu prefiro refatorar isso significativamente. Em geral, tento evitar comentários de uma linha que digam o que o próximo pedaço de código está fazendo. Em vez disso, divido isso em sua própria função, denominada semelhante ao comentário. Se você fizesse essa transformação, essa função forneceria uma visão geral de alto nível do que a função está fazendo, e cada uma das novas funções indicaria como você executa cada etapa. Eu sinto que muito mais importante do que qualquer oposição gotoé ter cada função no mesmo nível de abstração. Isso evita gotoé um bônus.
18720 David Stone

2
Você realmente não explicou como adicionar mais funções se livrar do Goto, os vários níveis de recuo e espúria if ...
Ricardo

Seria algo assim, usando a notação padrão de contêiner: 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.
11608 David Stone

3
O fato de alguém ter que dar exemplos de como certos algoritmos devem naturalmente usar o goto - é uma triste reflexão sobre o quão pouco o pensamento algorítmico acontece hoje !! Obviamente, o exemplo de @ Ricardo é (um de muitos) exemplos perfeitos de onde ir é elegante e óbvio.
Fattie

6

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.


1
Eu pude ver algo assim sendo útil em uma linguagem como C. No entanto, quando você tem o poder de construtores / destruidores de C ++, isso geralmente não é tão útil.
David Stone

1
"Em funções realmente complexas, relaxamos essa regra para permitir outros saltos adiante". Sem exemplo, isso soa como se você estivesse tornando funções complexas ainda mais complexas usando saltos. Não seria a abordagem "melhor" refatorar e dividir essas funções complexas?
MikeMB

1
Este foi o último objetivo para o qual eu usei goto. Eu não sinto falta de ir ao Java, porque ele tem finalmente o que pode fazer o mesmo trabalho.
Patricia Shanahan

5

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.


3

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 &subroutinevariaçã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 esse formulário gotopara 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 tailmodificador e outro que importará recurse 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 );
}

A maioria dos outros motivos para usar gotoé melhor realizada com outras palavras-chave.

Como redoum pouco de código:

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

Ou vá para lastum pouco de código de vários lugares:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

2

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.


2

Da mesma forma, ninguém jamais implementou a declaração "COME FROM" ....


8
Ou em C ++, C #, Java, JS, Python, Ruby .... etc etc etc. Somente eles chamam de "exceções".
cHao 9/12/11

2

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*/)

Isso não deveria /*empty*/ser stepfailed = 1? De qualquer forma, como isso é melhor que um do{}while(0)? Em ambos, você precisa breaksair dele (ou no seu stepfailed = 1; continue;). Parece desnecessário para mim.
Thomas Eding

2

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.


Certo. O "elefante na sala" é que o kernel do Linux, pelo amor de Deus, como um exemplo de uma base de código crítica mundial - é carregado com goto. Claro que é. Obviamente. O "anti-goto-meme" é apenas uma curiosidade de décadas atrás. É claro que existem várias coisas na programação (notavelmente "estática", na verdade "globais" e, por exemplo, "elseif") que podem ser abusadas por não profissionais. Então, se seu primo está aprendendo no programa 2, diga a eles "oh não use globais" e "nunca use elseif".
Fattie

O erro goto fail não tem nada a ver com goto. O problema é causado pela instrução if sem chaves depois. Praticamente qualquer cópia de instrução colada duas vezes teria causado um problema. Considero esse tipo de bracelete nu se for muito mais prejudicial do que ir.
muusbolla

2

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 {...} whilepareça mais familiar)
  • loopé necessária uma variável extra , usada em quatro locais
  • leva mais tempo para ler e entender o trabalho com o loop
  • o loopnão contém dados, apenas controla o fluxo da execução, que é menos compreensível do que o rótulo simples

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", 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:;

1

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.


Eu acho que a única forma de ir a lugares que você usaria no Perl é goto &subroutine. Que inicia a sub-rotina com o @_ atual, enquanto substitui a sub-rotina atual na pilha.
Brad Gilbert

1

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'.


11
Dizer "não consigo pensar em nenhum motivo" em seu argumento é formalmente en.wikipedia.org/wiki/Argument_from_ignorance , embora prefira a terminologia "prova por falta de imaginação".
APENAS MINHA OPINIÃO correta

2
@ APENAS MINHA OPINIÃO correta: é uma falácia lógica apenas se for empregada post-hoc. Do ponto de vista de um projetista de linguagem, pode ser um argumento válido avaliar o custo de incluir um recurso ( 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.
Konrad Rudolph

1
@KonradRudolph: IMHO, ter uma linguagem permitida, gotoexceto 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 gotopode não ser tão bom quanto usar outra estrutura, mas poder escrevê- gotolo ajudará a evitar "buracos na expressividade" - construções para as quais um idioma é incapaz de escrever código eficiente.
Supercat

1
@supercat Acho que somos de escolas fundamentalmente diferentes de design de idiomas. Oponho que as línguas sejam maximamente expressivas ao preço da compreensibilidade (ou correção). Prefiro ter uma linguagem restritiva do que permissiva.
Konrad Rudolph

1
@ thb Sim, claro que sim. Geralmente torna o código muito mais difícil de ler e raciocinar. Praticamente toda vez que alguém publica código que contém gotono site de revisão de código, a eliminação da gotosimplifica bastante a lógica do código.
Konrad Rudolph

1

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 FINALsem 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"


6
Esse era frequentemente o caso no BASIC da velha escola. Nas variantes modernas, porém, você não tem permissão para pular para o meio de outra função ... ou, em muitos casos, mesmo após a declaração de uma variável. Basicamente (trocadilhos não intencionados), as línguas modernas largaram os GOTOs "desenfreados" dos quais Dijkstra estava falando ... e a única maneira de usá-lo como ele estava argumentando contra é cometer outros pecados hediondos. :)
Chao

1

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".


3
É por isso que temos finallyblocos em c # #
John Saunders

^ como @JohnSaunders disse. Este é um exemplo de "usando goto porque uma linguagem não possui a construção de controle apropriada". No entanto, é um cheiro de código exigir MÚLTIPLOS pontos de goto perto do retorno. Existe um estilo de programação mais seguro (mais fácil de estragar) E não requer gotos, o que funciona bem mesmo em linguagens sem "finalmente": projete essas chamadas de "limpeza" para que elas sejam inofensivas quando não estiverem limpas é necessário. Fatore tudo, menos a limpeza, em um método, que usa o design de retorno múltiplo. Chame esse método e faça as chamadas de limpeza.
Home

Observe que a abordagem que descrevo requer um nível extra de chamada de método (mas apenas em um idioma que não possui finally). Como alternativa, use gotos, 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.
Página

@ToolmakerSteve, este não é um cheiro de código; de fato, é um padrão extremamente comum em C e é considerado uma das maneiras mais válidas de usar o goto. Você quer que eu crie 5 métodos, cada um com seu próprio teste if desnecessário, apenas para lidar com a limpeza dessa função? Agora você criou código E inchaço no desempenho. Ou você pode simplesmente usar goto.
muusbolla

@muusbolla - 1) "criar 5 métodos" - Não. Estou sugerindo um método único que limpe todos os recursos que tenham um valor não nulo. OU veja minha alternativa, que usa gotos 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 Cvocê 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..)
ToolmakerSteve

0

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 .


0

É ú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_literalpara 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 continuedeclaraçã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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.