Esse é um caso de uso decente para ir para C?


59

Eu realmente hesito em perguntar isso, porque não quero "solicitar debate, argumentos, sondagens ou discussão prolongada", mas sou novo em C e quero obter mais informações sobre os padrões comuns usados ​​na linguagem.

Recentemente, ouvi uma certa aversão ao gotocomando, mas também encontrei recentemente um caso de uso decente para ele.

Código como este:

error = function_that_could_fail_1();
if (!error) {
    error = function_that_could_fail_2();
    if (!error) {
        error = function_that_could_fail_3();
        ...to the n-th tab level!
    } else {
        // deal with error, clean up, and return error code
    }
} else {
    // deal with error, clean up, and return error code
}

Se a parte da limpeza for muito parecida, poderia ser escrito um pouco mais bonito (minha opinião?) Assim:

error = function_that_could_fail_1();
if(error) {
    goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
    goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
    goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code

Esse é um caso de uso comum ou aceitável gotoem C? Existe uma maneira diferente / melhor de fazer isso?



2
Mais do que o revestimento com açúcar, o compilador garante limpeza e verificação de erros, enquanto em C você deve fazê-lo manualmente, violando DRY e garantindo que em algum lugar você tenha preguiça de verificar o código de erro ou vá para a etiqueta errada ou algo assim.
DeadMG

3
@ Izkata: Não, o objetivo é ter funções (como destruidores) que são implicitamente chamadas pelo compilador quando uma exceção é lançada.
DeadMG

6
@DeadMG: Usar outro idioma nem sempre é uma opção.
Benjamin Kloster

13
Eu acho que com nomeação rótulo adequado, o exemplo pode parecer bastante decente:goto hell;
mosquito

Respostas:


48

A gotoinstrução (e seus rótulos correspondentes) são uma primitiva de controle de fluxo (junto com a execução condicional de uma instrução). Com isso, quero dizer que eles estão lá para permitir que você construa redes de controle de fluxo de programas. Você pode pensar neles como modelar as setas entre os nós de um fluxograma.

Alguns deles podem ser otimizados imediatamente, onde há um fluxo linear direto (você apenas usa uma sequência de instruções básicas). Outros padrões são melhor substituídos por construções de programação estruturada, quando disponíveis; se parecer um whileloop, use um whileloop , ok? Os padrões de programação estruturada são definitivamente pelo menos potencialmente mais claros de intenção do que uma bagunça de gotodeclarações.

No entanto, C não inclui todas as possíveis construções de programação estruturada. (Não está claro para mim que todos os relevantes já foram descobertos; a taxa de descoberta é lenta agora, mas eu hesitaria em dizer que todos foram encontrados.) Dos que conhecemos, C definitivamente não tem o try/ catch/ finallyestrutura (e também excepções). Também não possui vários níveis breakde loop. Esses são os tipos de coisas que gotopodem ser usados ​​para implementar. É possível usar outros esquemas para fazer isso também - sabemos que C possui um conjunto suficiente degotoprimitivas - mas geralmente envolvem a criação de variáveis ​​de flag e condições de loop ou guarda muito mais complexas; aumentar o emaranhado da análise de controle com a análise de dados dificulta a compreensão geral do programa. Isso também dificulta a otimização do compilador e a execução rápida da CPU (a maioria das construções de controle de fluxo - e definitivamente goto - é muito barata).

Portanto, embora você não deva usar a gotomenos que seja necessário, deve estar ciente de que ela existe e que pode ser necessária e, se você precisar, não deve se sentir muito mal. Um exemplo de caso em que é necessário é a desalocação de recursos quando uma função chamada retorna uma condição de erro. (Ou seja, try/ finally.) É possível escrever que, sem gotofazer isso, pode haver desvantagens próprias, como os problemas de manutenção. Um exemplo do caso:

int frobnicateTheThings() {
    char *workingBuffer = malloc(...);
    int i;

    for (i=0 ; i<numberOfThings ; i++) {
        if (giveMeThing(i, workingBuffer) != OK)
            goto error;
        if (processThing(workingBuffer) != OK)
            goto error;
        if (dispatchThing(i, workingBuffer) != OK)
            goto error;
    }

    free(workingBuffer);
    return OK;

  error:
    free(workingBuffer);
    return OOPS;
}

O código pode ser ainda mais curto, mas é suficiente para demonstrar o ponto.


4
+1: Em C goto, tecnicamente, nunca é "necessário" - sempre há uma maneira de fazê-lo, fica confuso ..... Para um conjunto robusto de diretrizes para o uso de goto, veja MISRA C.
mattnz

11
Você prefere try/catch/finallyque gotoapesar da (como ele pode se espalhar por todo múltiplas funções / módulos) forma semelhante, ainda mais difundida de código espaguete isso é possível usando try/catch/finally?
autista

65

Sim.

É usado, por exemplo, no kernel do linux. Aqui está um email do final de um tópico de quase uma década atrás , destacando o meu:

De: Robert Love
Assunto: Re: alguma chance de 2.6.0-test *?
Data: 12 de janeiro de 2003 17:58:06 -0500

No domingo, 12/01/2003 às 17:22, Rob Wilkens escreveu:

Eu digo "por favor, não use goto" e, em vez disso, tenha uma função "cleanup_lock" e adicione-a antes de todas as instruções de retorno. Não deve ser um fardo. Sim, está pedindo ao desenvolvedor que trabalhe um pouco mais, mas o resultado final é um código melhor.

Não, é nojento e incha o kernel . Ele destaca um monte de lixo eletrônico para N caminhos de erro, em vez de ter o código de saída uma vez no final. A pegada de cache é a chave e você acabou de matá-la.

Nem é mais fácil de ler.

Como argumento final, ele não nos permite executar de maneira limpa o vento usual do tipo pilha e relaxar , ou seja,

        do A
        if (error)
            goto out_a;
        do B
        if (error)
            goto out_b;
        do C
        if (error)
            goto out_c;
        goto out;
        out_c:
        undo C
        out_b:
        undo B:
        out_a:
        undo A
        out:
        return ret;

Agora pare com isso.

Robert Love

Dito isso, requer muita disciplina para não criar código espaguete depois que você se acostumar a usar o goto; portanto, a menos que você esteja escrevendo algo que exija velocidade e pouco espaço na memória (como um kernel ou sistema incorporado), você realmente deve pense nisso antes de escrever o primeiro passo.


21
Observe que um kernel é diferente de um programa que não é do kernel em relação à prioridade entre velocidade bruta e legibilidade. Em outras palavras, eles criaram perfil e descobriram que precisam otimizar seu código para velocidade com goto.

11
Usando a pilha sem vento para lidar com a limpeza de erros sem realmente empurrar para a pilha! Este é um ótimo uso do goto.
mike30

11
@ user1249, lixo, você não pode criar um perfil de todos os aplicativos {passados, existentes, futuros} que usam uma parte do código {library, kernel}. Você simplesmente precisa ser rápido.
Pacerier

11
Não relacionado: Estou impressionado com o modo como as pessoas podem usar as listas de discussão para fazer qualquer coisa, sem falar em projetos tão grandes. É tão ... primitivo. Como as pessoas entram com o quartel de bombeiros ?!
Alexander

2
Não estou tão preocupado com a moderação. Se alguém é macio o suficiente para ser rejeitado por algum idiota na internet, seu projeto provavelmente será melhor sem ele. Estou mais preocupado com a impraticabilidade de acompanhar a enxurrada de mensagens recebidas e como você poderia ter uma discussão natural de retorno com tão poucas ferramentas para acompanhar as cotações, por exemplo.
Alexander

14

Na minha opinião, o código que você postou é um exemplo de uso válido goto, porque você apenas pula para baixo e o usa como um manipulador de exceção primitivo.

No entanto , por causa do velho debate sobre o goto, os programadores evitam gotohá cerca de 40 anos e, portanto, não são usados ​​para ler código com o goto. Esta é uma razão válida para evitar ir para lá: simplesmente não é o padrão.

Eu teria reescrito o código como algo mais facilmente lido pelos programadores em C:

Error some_func (void)
{
  Error error;
  type_t* resource = malloc(...);

  error = some_other_func (resource);

  free (resource);

  /* error handling can be placed here, or it can be returned to the caller */

  return error;
}


Error some_other_func (type_t* resource)  // inline if needed
{
  error = function_that_could_fail_1();
  if(error)
  {
    return error;
  }

  /* ... */

  error = function_that_could_fail_2();
  if(error)
  {
    return error;
  }

  /* ... */

  return ok;
}

Vantagens deste projeto:

  • A função que está realizando o trabalho real não precisa se preocupar com tarefas irrelevantes para seu algoritmo, como alocar dados.
  • O código parecerá menos estranho para os programadores C, uma vez que eles têm medo de ir para rótulos e etiquetas.
  • Você pode centralizar o tratamento e a desalocação de erros no mesmo local, fora da função que executa o algoritmo. Não faz sentido para uma função lidar com seus próprios resultados.


9

Em Java, você faria assim:

makeCalls:  {
    error = function_that_could_fail_1();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_2();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_3();
    if (error) {
        break makeCalls;
    }
    ...
    return 0;  // No error code.
}
// deal with error if it exists, clean up
// return error code

Eu uso muito isso. Por mais que eu não gotogoste, na maioria das outras linguagens de estilo C, uso seu código; não há outra boa maneira de fazer isso. (Saltar de loops aninhados é um caso semelhante; em Java, uso um rotulado breake em qualquer outro lugar em que uso um goto.)


3
Ah, isso é uma estrutura de controle elegante.
11789 Bryan Boettcher #

4
Isso é realmente interessante. Eu normalmente pensaria em usar a estrutura try / catch / finalmente para isso em java (lançando exceções em vez de quebrar).
Robz

5
Isso é realmente ilegível (pelo menos para mim). Se presente, as exceções são muito melhores.
M3th0dman

11
@ m3th0dman Concordo com você neste exemplo em particular (tratamento de erros). Mas há outros casos (não excepcionais) em que esse idioma pode ser útil.
Konrad Rudolph

11
As exceções são caras, elas precisam gerar um erro, rastreamento de pilha e muito mais lixo. Essa quebra de etiqueta fornece uma saída limpa de um loop de verificação. a menos que alguém não se importe com memória e velocidade, então, por tudo que eu me importo, use uma exceção.
Tschallacka

8

Eu acho que é um caso de uso decente, mas no caso de "erro" nada mais é que um valor booleano, há uma maneira diferente de realizar o que você deseja:

error = function_that_could_fail_1();
error = error || function_that_could_fail_2();
error = error || function_that_could_fail_3();
if(error)
{
     // do cleanup
}

Isso faz uso da avaliação de curto-circuito de operadores booleanos. Se isso "melhor", depende do seu gosto pessoal e de como você está acostumado a esse idioma.


11
O problema disso é que erroro valor desse valor pode se tornar sem sentido com todos os ORs.
James

@ James: editei a minha resposta devido ao seu comentário
Doc Brown

11
Isto não é suficiente. Se ocorreu um erro durante a primeira função, não quero executar a segunda ou terceira função.
Robz

2
Se, com avaliação de mão curta , você quer dizer avaliação de curto-circuito , isso não é exatamente o que é feito aqui devido ao uso de OR bit a bit em vez de OR lógico.
Chris diz Reinstate Monica

11
@ChristianRau: obrigado, editei minha resposta de acordo #
Doc Brown

6

O guia de estilo linux fornece razões específicas para o uso de gotoacordo com o seu exemplo:

https://www.kernel.org/doc/Documentation/process/coding-style.rst

A lógica para usar gotos é:

  • declarações incondicionais são mais fáceis de entender e seguir
  • aninhamento é reduzido
  • erros ao não atualizar pontos de saída individuais ao impedir modificações
  • salva o trabalho do compilador para otimizar o código redundante;)

Isenção de responsabilidade Não devo compartilhar meu trabalho. Os exemplos aqui são um pouco inventados, então tenha paciência comigo.

Isso é bom para o gerenciamento de memória. Recentemente, trabalhei em códigos que alocavam memória dinamicamente (por exemplo, um char *retornado por uma função). Uma função que analisa um caminho e verifica se o caminho é válido analisando os tokens do caminho:

tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        free(var1);
        free(var2);
        ...
        free(varN);
        return 1;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            free(var1);
            free(var2);
            ...
            free(varN);
            return 1;
        } else {
            free(var1);
            free(var2);
            ...
            free(varN);
            return 0;
        }
    }
    token = strtok(NULL,delim);
}

free(var1);
free(var2);
...
free(varN);
return 1;

Agora, para mim, o código a seguir é muito melhor e mais fácil de manter, se você precisar adicionar um varNplus1:

int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        retval = 1;
        goto out_free;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            retval = 1;
            goto out_free;
        } else {
            retval = 0;
            goto out_free;
        }
    }
    token = strtok(NULL,delim);
}

out_free:
free(var1);
free(var2);
...
free(varN);
return retval;

Agora, o código tinha todos os tipos de problemas com ele, ou seja, N estava acima de 10, e a função tinha mais de 450 linhas, com 10 níveis de aninhamento em alguns lugares.

Mas eu ofereci ao meu supervisor para refatorá-lo, o que eu fiz e agora são várias funções curtas, e todas elas têm o estilo linux

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 == NULL ){
        retval = 0;
        goto out;
    }

    if( isValid(var1) ){
         retval = some_function(var1);
         goto out_free;
    }

    if( isGood(var1) ){
         retval = 0;
         goto out_free;
    }

out_free:
    free(var1);
out:
    return retval;
}

Se considerarmos o equivalente sem gotos:

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 != NULL ){

       if( isValid(var1) ){
            retval = some_function(var1);
       } else {
          if( isGood(var1) ){
               retval = 0;
          }
       }
       free(var1);

    } else {
       retval = 0;
    }

    return retval;
}

Para mim, no primeiro caso, é óbvio para mim que, se a primeira função retornar NULL, estaremos fora daqui e voltaremos 0. No segundo caso, eu tenho que rolar para baixo para ver que o if contém toda a função. Concedido o primeiro indica isso estilisticamente (o nome " out") e o segundo faz sintaticamente. O primeiro é ainda mais óbvio.

Além disso, eu prefiro ter free()instruções no final de uma função. Isso ocorre em parte porque, na minha experiência, as free()declarações no meio de funções cheiram mal e indicam para mim que eu deveria criar uma sub-rotina. Nesse caso, eu criei var1em minha função e não free()consegui em uma sub-rotina, mas é por isso que o goto out_freeestilo de sair é tão prático.

Eu acho que os programadores precisam ser educados acreditando que gotosão maus. Então, quando estiverem maduros o suficiente, devem procurar o código fonte do Linux e ler o guia de estilo do Linux.

Devo acrescentar que uso esse estilo de maneira muito consistente, toda função tem um int retval, um out_freelabel e um out label. Devido à consistência estilística, a legibilidade é aprimorada.

Bônus: quebra e continua

Digamos que você tenha um loop while

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);

    if( functionC(var1, var2){
         count++
         continue;
    }

    ...
    a bunch of statements
    ...

    count++;
    free(var1);
    free(var2);
}

Há outras coisas erradas nesse código, mas uma coisa é a instrução continue. Eu gostaria de reescrever a coisa toda, mas fui encarregado de modificá-la em um pequeno caminho. Levaria dias para refatorá-lo de uma maneira que me satisfizesse, mas a mudança real foi de cerca de meio dia de trabalho. O problema é que, mesmo se nós ' continue' ainda precisamos libertar var1e var2. Eu tive que adicionar um var3, e isso me fez querer vomitar ter que espelhar as declarações free ().

Eu era um estagiário relativamente novo na época, mas estava olhando o código-fonte do Linux há algum tempo, então perguntei ao meu supervisor se eu poderia usar uma declaração goto. Ele disse que sim, e eu fiz isso:

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);
    var3 = newFunction(line,count);

    if( functionC(var1, var2){
         goto next;
    }

    ...
    a bunch of statements
    ...
next:
    count++;
    free(var1);
    free(var2);
}

Eu acho que continua bom, na melhor das hipóteses, mas para mim eles são como um goto com uma etiqueta invisível. O mesmo vale para pausas. Eu ainda preferiria continuar ou interromper, a menos que, como foi o caso aqui, o force a espelhar modificações em vários lugares.

E devo acrescentar que esse uso goto next;e o next:rótulo são insatisfatórios para mim. Eles são meramente melhores do que espelhar o free()'s e as count++declarações.

gotoQuase sempre estão errados, mas é preciso saber quando eles são bons de usar.

Uma coisa que não discuti é o tratamento de erros, coberto por outras respostas.

atuação

Pode-se observar a implementação de strtok () http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c

#include <stddef.h>
#include <string.h>

char *
strtok(s, delim)
    register char *s;
    register const char *delim;
{
    register char *spanp;
    register int c, sc;
    char *tok;
    static char *last;


    if (s == NULL && (s = last) == NULL)
        return (NULL);

    /*
     * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
     */
cont:
    c = *s++;
    for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
        if (c == sc)
            goto cont;
    }

    if (c == 0) {       /* no non-delimiter characters */
        last = NULL;
        return (NULL);
    }
    tok = s - 1;

    /*
     * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
     * Note that delim must have one NUL; we stop if we see that, too.
     */
    for (;;) {
        c = *s++;
        spanp = (char *)delim;
        do {
            if ((sc = *spanp++) == c) {
                if (c == 0)
                    s = NULL;
                else
                    s[-1] = 0;
                last = s;
                return (tok);
            }
        } while (sc != 0);
    }
    /* NOTREACHED */
}

Por favor, corrija-me se estiver errado, mas acredito que o cont:rótulo e a goto cont;declaração existem para desempenho (eles certamente não tornam o código mais legível). Eles podem ser substituídos por código legível fazendo

while( isDelim(*s++,delim));

pular delimitadores. Mas, para ser o mais rápido possível e evitar chamadas de função desnecessárias, elas fazem dessa maneira.

Eu li o jornal de Dijkstra e acho bastante esotérico.

google "dijkstra goto declaração considerada prejudicial" porque não tenho reputação suficiente para postar mais de 2 links.

Eu o vi citado como uma razão para não usar o goto e a leitura dele não mudou nada, na medida em que meus usos do goto são considerados.

Adendo :

Eu criei uma regra clara enquanto pensava em tudo isso sobre continuações e interrupções.

  • Se em um loop while, você tem uma continuação, o corpo do loop while deve ser uma função e a continuação uma declaração de retorno.
  • Se em um loop while, você possui uma instrução break, o próprio loop while deve ser uma função e a interrupção deve se tornar uma declaração de retorno.
  • Se você tiver os dois, algo pode estar errado.

Nem sempre é possível devido a problemas de escopo, mas descobri que isso facilita muito o raciocínio sobre meu código. Eu havia notado que sempre que um loop de tempo interrompia ou continuava, isso me dava um mau pressentimento.


2
+1, mas posso discordar em um ponto? "Eu acho que os programadores precisam ser educados acreditando que os gotos são maus". Talvez sim, mas eu aprendi a programar em BASIC, com números de linha e GOTOs, sem um editor de texto, em 1975. Conheci a programação estruturada dez anos depois, após o que levei um mês para parar de usar o GOTO sozinho, sem qualquer pressão para parar. Hoje, uso o GOTO algumas vezes por ano por várias razões, mas não aparece muito. Não ter sido educado a acreditar que GOTO é mau não me causou nenhum dano, e pode até ter feito algum bem. Sou apenas eu.
Th

11
Eu acho que você está certo sobre isso. Fui criado com a idéia de que os GOTOs não deveriam ser usados ​​e, por pura coincidência, eu estava navegando no código-fonte do Linux no momento em que estava trabalhando no código que tinha essas funções com vários pontos de saída com memória para liberar. Caso contrário, eu nunca saberia sobre essas técnicas.
Philippe Carphin 30/10

11
@thb Além disso, uma história engraçada, pedi ao meu supervisor na época, como estagiário, permissão para usar os GOTOs e certifiquei-me de explicar que iria usá-los de uma maneira específica, como na maneira como é usado no Kernel do Linux e ele disse: "Ok, isso faz sentido, e também, eu não sabia que você poderia usar os GOTO em C".
Philippe Carphin 30/10

11
@ thb Eu não sei se é bom ir para loops (em vez de quebrar os loops) como este ? Bem, é um mau exemplo, mas acho que o quicksort com instruções goto (exemplo 7a) na Programação Estruturada de Knuth com Instruções não é muito compreensível.
Yai0Phah

@ Yai0Phah vou explicar meu ponto, mas minha explicação não diminui seu bom exemplo 7a! Eu aprovo o exemplo. Ainda assim, alunos do segundo ano mandão gostam de dar palestras às pessoas sobre irem. É difícil encontrar um uso prático do goto desde 1985 que cause problemas significativos, ao passo que se pode encontrar gotos inofensivos que facilitam o trabalho do programador. De qualquer forma, raramente aparece na programação moderna que, quando isso ocorre, meu conselho é que, se você quiser usá-lo, provavelmente deve usá-lo. Goto está bem. O principal problema com o goto é que alguns acreditam que a depreciação do goto os faz parecer inteligentes.
thb

5

Pessoalmente, eu refatoraria isso mais ou menos assim:

int DoLotsOfStuffThatCouldFail (paramstruct *params)
{
    int errcode = EC_NOERROR;

    if ((errcode = FunctionThatCouldFail1 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail2 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail3 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail4 (params)) != EC_NOERROR) return errcode;

    return EC_NOERROR;
}

void DoStuff (paramstruct *params)
{
    int errcode = EC_NOERROR;

    InitStuffThatMayNeedToBeCleaned (params);

    if ((errcode = DoLotsOfStuffThatCouldFail (params)) != EC_NOERROR)
    {
         CleanupAfterError (params, errcode);
    }
}

Isso seria mais motivado ao evitar o aninhamento profundo do que evitar o goto (no entanto, o IMO é um problema pior com o primeiro exemplo de código) e, é claro, dependeria da possibilidade de CleanupAfterError estar fora do escopo (neste caso, "parâmetros" seja uma estrutura contendo alguma memória alocada que você precisa liberar, um ARQUIVO * que você precisa fechar ou o que for).

Uma grande vantagem que vejo com essa abordagem é que é mais fácil e mais limpo dividir uma etapa extra hipotética futura entre, digamos, FTCF2 e FTCF3 (ou remover uma etapa atual existente), para que ela se melhore melhor com a manutenção (e a pessoa que herda meu código, não querendo me linchar!) - vá para o lado, a versão aninhada não tem isso.


11
Não afirmei isso na minha pergunta, mas é possível que os FTCFs NÃO tenham os mesmos parâmetros, tornando esse padrão um pouco mais complicado. Obrigado embora.
Robz

3

Dê uma olhada nas diretrizes de codificação C da MISRA (Motor Industry Software Reliability Association) que permitem ir sob critérios rígidos (que seu exemplo atende)

Onde eu trabalho, o mesmo código seria escrito - não é preciso ir - Evitar um debate religioso desnecessário sobre eles é uma grande vantagem em qualquer software house.

error = function_that_could_fail_1();
if(!error) {
  error = function_that_could_fail_2();
}
if(!error) {
  error = function_that_could_fail_3();
} 
if(!error) {
...
if (error) {
  cleanup:
} 

ou para "goto in drag" - algo ainda mais desonesto do que goto, mas contorna o "No goto ever !!!" acampamento) "Certamente deve estar OK, não usa Goto" ....

do {
  if (error = function_that_could_fail_1() ){
    break 
  }
  if (error = function_that_could_fail_2() ){
    break 
  }
  ....... 
} while (0) 
cleanup();
.... 

Se as funções tiverem o mesmo tipo de parâmetro, coloque-as em uma tabela e use um loop -


2
As diretrizes atuais do MISRA-C: 2004 não permitem ir de forma alguma (consulte a regra 14.4). Observe que o comitê do MISRA sempre esteve confuso sobre isso, eles não sabem em que pé pisar. Primeiro, eles proibiram incondicionalmente o uso do goto, continuaram etc. Mas no rascunho para o próximo MISRA 2011 eles querem permitir novamente. Como nota de rodapé, observe que o MISRA proíbe a atribuição dentro de instruções if, por muito boas razões, pois é muito mais perigoso do que qualquer uso de goto.

11
Do ponto de vista analítico, adicionar um sinalizador a um programa equivale a duplicar todo o código de código em que o sinalizador está no escopo, fazer com que cada if(flag)cópia copie o ramo "if" e que as instruções correspondentes na outra cópia utilizem o " outro". Ações que definem e limpam a bandeira são realmente "gotos" que saltam entre essas duas versões do código. Há momentos em que o uso de sinalizadores é mais limpo do que qualquer alternativa, mas adicionar um sinalizador para salvar um gotoalvo não é uma boa opção.
Supercat 27/03

1

Também uso gotose o do/while/continue/breakhackery alternativo seria menos legível.

gotos têm a vantagem de seus alvos terem um nome e lerem goto something;. Isso pode ser mais legível do que breakou continuese você não está realmente parando ou continuando alguma coisa.


4
Em qualquer lugar dentro de uma do ... while(0)ou outra construção que não seja um loop real, mas uma tentativa contida de impedir o uso de a goto.
aib

11
Ah, obrigado, eu não conhecia essa marca específica de "Por que diabos alguém faria isso ?!" constrói ainda.
Benjamin Kloster

2
Normalmente, o hackery do / while / continue / break só se torna ilegível quando o módulo que o contém é muito demorado em primeiro lugar.
John R. Strohm

2
Não consigo encontrar nada nisso como justificativa para usar goto. Quebrar e continuar têm uma conseqüência óbvia. ir ... onde? Onde está o rótulo? Quebre e vá dizer exatamente onde está o próximo passo e o próximo.
Rig

11
É claro que o rótulo deve estar visível dentro do loop. Concordo com a parte do comentário de John R. Strohm. E o seu argumento, traduzido para hackery de loop, torna-se "Quebre de quê? Isso não é um loop!". De qualquer forma, isso está se tornando o que o OP temia, então estou abandonando a discussão.
aib

-1
for (int y=0; y<height; ++y) {
    for (int x=0; x<width; ++x) {
        if (find(x, y)) goto found;
    }
}
found:

Se houver apenas um loop, breakfunciona exatamente como goto, embora não tenha estigma.
9000

6
-1: Primeiro, xey estão fora do escopo encontrados :, portanto, isso não ajuda em nada. Segundo, com o código escrito, o fato de você ter chegado encontrado: não significa que você encontrou o que estava procurando.
John R. Strohm

É porque este é o menor exemplo que eu poderia pensar no caso de romper um número múltiplo de loops. Sinta-se à vontade para editá-lo para obter uma etiqueta melhor ou uma verificação concluída.
aib

11
Mas também tenha em mente que as funções C não são necessariamente livres de efeitos colaterais.
aib

11
@ JohnR.Strohm Isso não faz muito sentido ... O rótulo 'encontrado' é usado para quebrar o loop, não para verificar as variáveis. Se eu quisesse verificar as variáveis, poderia fazer algo assim: for (int y = 0; y <altura; ++ y) {for (int x = 0; x <largura; ++ x) {if (find ( x, y)) {doSomeThingWith (x, y); foi encontrado; }}} encontrado:
YoYoYonnY

-1

Sempre haverá campos que dizem que uma maneira é aceitável e outra que não é. As empresas para as quais trabalhei desaprovaram ou fortemente desencorajaram o uso. Pessoalmente, não consigo pensar em nenhum momento em que usei um, mas isso não significa que eles sejam ruins , apenas outra maneira de fazer as coisas.

Em C, normalmente faço o seguinte:

  • Teste as condições que podem impedir o processamento (entradas incorretas, etc.) e "retornar"
  • Execute todas as etapas que requerem alocação de recursos (por exemplo, mallocs)
  • Execute o processamento, onde várias etapas verificam o sucesso
  • Libere quaisquer recursos, se alocados com êxito
  • Retornar quaisquer resultados

Para o processamento, usando seu exemplo goto, eu faria o seguinte:

error = function_that_could_fail_1 (); if (! error) {error = function_that_could_fail_2 (); } if (! error) {error = function_that_could_fail_3 (); }

Não há aninhamento e, dentro das cláusulas if, você pode fazer qualquer relatório de erros, se a etapa gerar um erro. Portanto, não precisa ser "pior" do que um método usando gotos.

Eu ainda tenho que me deparar com um caso em que alguém tem gotos que não podem ser feitos com outro método e é tão legível / compreensível e essa é a chave, IMHO.

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.