Это достойный вариант использования Goto в C?

59

Я действительно стесняюсь спросить об этом, потому что я не хочу «требовать дебатов, аргументов, опросов или расширенных дискуссий», но я новичок в C и хочу получить больше понимания общих шаблонов, используемых в языке.

Недавно я услышал некоторое отвращение к gotoкоманде, но я также недавно нашел достойный вариант использования для нее.

Код как это:

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
}

Если часть очистки очень похожа, можно было бы написать немного красивее (мое мнение?) Примерно так:

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

Это распространенный или приемлемый вариант использования gotoC? Есть ли другой / лучший способ сделать это?

Robz
источник
3
Смотрите также этот вопросмой ответ ).
Кит Томпсон
2
Больше, чем просто сахарное покрытие, компилятор гарантирует очистку и проверку ошибок, в то время как в C вы должны делать это вручную, нарушая DRY и гарантируя, что в каком-то месте вам было лень проверять код ошибки или перейти на неправильную метку или что-то в этом роде.
DeadMG
3
@Izkata: Нет, дело в том, чтобы иметь функции (например, деструкторы), которые неявно вызываются компилятором при возникновении исключения.
DeadMG
6
@DeadMG: использование другого языка не всегда вариант.
Бенджамин Клостер
13
Я думаю, что с соответствующим наименованием метки, пример может выглядеть вполне прилично:goto hell;
gnat

Ответы:

48

gotoУтверждение (и его соответствующие метки) являются управлением потоком примитива (наряду с условным исполнением заявления). Под этим я подразумеваю, что они существуют для того, чтобы вы могли создавать сети управления потоком программ. Вы можете думать о них как о моделировании стрелок между узлами потоковой диаграммы.

Некоторые из них могут быть оптимизированы немедленно, когда есть прямой линейный поток (вы просто используете последовательность основных операторов). Другие шаблоны лучше всего заменять структурами структурированного программирования, где они доступны; если это похоже на whileцикл, используйте whileцикл , хорошо? Шаблоны структурированного программирования определенно, по крайней мере, потенциально более понятны, чем путаница gotoутверждений.

Тем не менее C не включает в себя все возможные конструкции структурированного программирования. (Мне не ясно, все ли соответствующие из них были обнаружены; скорость обнаружения сейчас медленная, но я не решусь сразу сказать, что все они были найдены.) Из тех, о которых мы знаем, С определенно не хватает try/ catch/ finallyструктура (и исключения тоже). В нем также отсутствует многоуровневый breakцикл. Это те вещи, которые gotoмогут быть использованы для реализации. Можно использовать и другие схемы, чтобы сделать это тоже - мы знаем, что C имеет достаточный наборgotoпримитивы - но они часто включают создание флаговых переменных и гораздо более сложные условия цикла или защиты; увеличение степени запутанности контрольного анализа с анализом данных усложняет понимание программы в целом. Это также затрудняет оптимизацию компилятора и быстрое выполнение ЦП (большинство конструкций управления потоками - и, безусловно, goto - очень дешевые).

Таким образом, хотя вы не должны использовать без gotoнеобходимости, вы должны знать, что он существует и что он может быть необходим, и если вам это нужно, вы не должны чувствовать себя слишком плохо. Примером случая, когда это необходимо, является освобождение ресурса, когда вызываемая функция возвращает условие ошибки. (То есть try/ finally.) Можно написать, что без этого, gotoно без этого, могут быть свои недостатки, такие как проблемы его обслуживания. Пример кейса:

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;
}

Код может быть еще короче, но этого достаточно, чтобы продемонстрировать это.

Donal Fellows
источник
4
+1: в Си goto технически никогда не «нужен» - всегда есть способ сделать это, он становится грязным ..... Для надежного набора рекомендаций по использованию goto посмотрите на MISRA C.
mattnz
1
Вы бы предпочли , try/catch/finallyчтобы goto, несмотря на подобных, но более распространенных (как она может распространяться по нескольким функциям / модулей) форм запутанного кода , который возможен с помощью try/catch/finally?
аутист
65

Да.

Он используется, например, в ядре Linux. Вот электронное письмо от конца ветки почти десятилетия назад , написанное жирным шрифтом:

От: Роберт Лав
Тема: Re: есть ли шанс в 2.6.0-тесте *?
Дата: 12 января 2003 г. 17:58:06 -0500

On Sun, 2003-01-12 в 17:22, Роб Уилкенс написал:

Я говорю «пожалуйста, не используйте goto», вместо этого добавьте функцию «cleanup_lock» и добавьте ее перед всеми операторами return. Это не должно быть бременем. Да, он просит разработчика работать немного усерднее, но конечный результат - лучший код.

Нет, это грубо, и это раздувает ядро . Он содержит кучу мусора для N путей ошибок, в отличие от наличия кода выхода один раз в конце. След кэша является ключевым, и вы его просто убили.

Не легче читать.

В качестве последнего аргумента, он не позволяет нам делать обычные стекистические ветры и раскручиваться , т.е.

        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;

Теперь прекрати это.

Роберт Лав

Тем не менее, требуется много дисциплины, чтобы не создавать спагетти-код, как только вы привыкнете к использованию goto, поэтому, если вы не пишете что-то, что требует скорости и небольшого объема памяти (например, ядра или встроенной системы), вы должны действительно Подумайте об этом, прежде чем написать первый goto.

Izkata
источник
21
Обратите внимание, что ядро ​​отличается от неядерной программы в том, что касается приоритета сырой скорости по сравнению с удобочитаемостью. Другими словами, они УЖЕ профилированы и обнаружили, что им нужно оптимизировать свой код для скорости с помощью Goto.
11
Использование размотки стека для обработки ошибок без фактического нажатия на стек! Это потрясающее использование goto.
mike30
1
@ user1249, Мусор, вы не можете профилировать каждое {прошлое, существующее, будущее} приложение, которое использует часть кода {библиотека, ядро}. Вам просто нужно быть быстрым.
Pacerier
1
Несвязанный: я поражен, как люди могут использовать списки рассылки, чтобы сделать что-нибудь, не говоря уже о таких масштабных проектах. Это так ... примитивно. Как люди приходят с огнем сообщений ?!
Александр
2
Я не так обеспокоен модерацией. Если кто-то достаточно мягок, чтобы быть отвергнутым каким-то мудаком в Интернете, ваш проект, вероятно, будет лучше без них. Меня больше беспокоит непрактичность того, чтобы идти в ногу с заграждением входящих сообщений, и то, как вы могли бы провести естественную обратную дискуссию с таким небольшим количеством инструментов для отслеживания цитат, например.
Александр
14

По моему мнению, код, который вы разместили, является примером правильного использования goto, потому что вы только перепрыгиваете вниз и используете его только как примитивный обработчик исключений.

Однако из-за старых дебатов о goto программисты избегали gotoоколо 40 лет, и поэтому они не привыкли читать код с помощью goto. Это веская причина, чтобы избежать перехода: это просто не стандарт.

Я бы переписал код как нечто более простое для чтения программистами 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;
}

Преимущества этого дизайна:

  • Функция, выполняющая реальную работу, не должна заниматься задачами, не относящимися к ее алгоритму, такими как распределение данных.
  • Код будет выглядеть менее чуждым для C-программистов, так как они боятся goto и меток.
  • Вы можете централизовать обработку ошибок и освобождение в одном месте, вне функции, выполняющей алгоритм. Для функции не имеет смысла обрабатывать свои собственные результаты.
Док Браун
источник
11

Известной статьей, описывающей случай правильного использования, было Структурное программирование с GOTO Заявление Дональда Кнута (Стэнфордский университет). Статья появилась в те дни, когда использование GOTO считалось грехом для некоторых, и когда движение за структурированное программирование было на пике. Возможно, вы захотите взглянуть на GoTo считается вредным.

Без шансов
источник
9

В Java вы бы сделали это так:

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

Я использую это много. Как бы мне это не gotoнравилось, в большинстве других языков C-стиля я использую ваш код; нет другого хорошего способа сделать это. (Прыжок из вложенных циклов - это аналогичный случай; в Java я использую метку, breakа везде я использую goto.)

RalphChapin
источник
3
О, это аккуратная структура управления.
Брайан Бетчер
4
Это действительно интересно. Я обычно думал бы об использовании структуры try / catch / finally для этого в Java (бросая исключения вместо взлома).
Робз
5
Это действительно нечитаемо (по крайней мере, для меня). Если присутствуют, исключения гораздо лучше.
m3th0dman
1
@ m3th0dman Я согласен с вами в этом конкретном примере (обработка ошибок). Но есть и другие (не исключительные) случаи, когда эта идиома может пригодиться.
Конрад Рудольф
1
Исключения являются дорогостоящими, они должны генерировать ошибку, трассировку стека и намного больше мусора. Этот разрыв метки дает чистый выход из цикла проверки. если только не заботятся о памяти и скорости, то для всех я использую исключение.
Чаллака
8

Я думаю , что это достойный случай использования, но в случае «ошибка» не что иное , как логическое значение, есть другой способ сделать то , что вы хотите:

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

Это использует оценку короткого замыкания логических операторов. Если это «лучше», зависит от вашего личного вкуса и от того, как вы привыкли к этой идиоме.

Док Браун
источник
1
Проблема в том, что errorзначение может стать бессмысленным со всеми операциями ИЛИ.
Джеймс
@James: отредактировал мой ответ из-за вашего комментария
Док Браун
1
Этого недостаточно. Если во время первой функции произошла ошибка, я не хочу выполнять вторую или третью функцию.
Робз
2
Если с короткой рукой оценки вы имеете в виду короткого замыкания оценки, это точно не то , что делается здесь в связи с использованием побитового OR вместо логического ИЛИ.
Крис говорит восстановить Монику
1
@ChristianRau: спасибо, соответственно отредактировал мой ответ
Док Браун
6

Руководство по стилю linux дает конкретные причины для использования goto, которые соответствуют вашему примеру:

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

Обоснование использования gotos:

  • безусловные заявления легче понять и следовать
  • вложенность уменьшается
  • ошибки, не обновляя отдельные точки выхода при внесении изменений, предотвращаются
  • сохраняет работу компилятора для оптимизации избыточного кода;)

Отказ от ответственности Я не должен делиться своей работой. Приведенные здесь примеры немного надуманы, поэтому, пожалуйста, потерпите меня.

Это хорошо для управления памятью. Недавно я работал над кодом, в котором динамически выделялась память (например, char *возвращаемая функцией). Функция, которая просматривает путь и выясняет, является ли путь допустимым, анализируя токены пути:

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;

Теперь для меня следующий код намного приятнее и его легче поддерживать, если вам нужно добавить 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;

Теперь в коде были и другие проблемы, а именно то, что N было где-то выше 10, а функция была более 450 строк с 10 уровнями вложенности в некоторых местах.

Но я предложил своему супервайзеру провести его рефакторинг, что я и сделал, и теперь это набор функций, которые все короткие, и все они имеют стиль 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;
}

Если мы рассмотрим эквивалент без 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;
}

Для меня, в первом случае, для меня очевидно, что если первая функция вернется NULL, мы уйдем отсюда и вернемся 0. Во втором случае мне нужно прокрутить вниз, чтобы увидеть, что if содержит всю функцию. Конечно, первый указывает на это для меня стилистически (название « out»), а второй - на синтаксический. Первый еще более очевиден.

Кроме того, я предпочитаю иметь free()операторы в конце функции. Это отчасти потому, что, по моему опыту, free()операторы в середине функций плохо пахнут и указывают мне, что я должен создать подпрограмму. В этом случае я создал var1свою функцию и не смог free()ее выполнить в подпрограмме, но поэтому goto out_freeстиль goto out настолько практичен.

Я думаю, что программистов нужно воспитывать, полагая, что gotoэто зло. Затем, когда они станут достаточно зрелыми, им следует просмотреть исходный код Linux и прочитать руководство по стилю linux.

Я должен добавить, что я использую этот стиль очень последовательно, каждая функция имеет int retval, out_freelabel и out label. Благодаря стилистической последовательности улучшается читаемость.

Бонус: ломается и продолжается

Скажем, у вас есть время цикла

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);
}

Есть другие вещи не так с этим кодом, но одна вещь, это оператор continue. Я хотел бы переписать все это, но мне было поручено немного изменить его. Мне потребовалось бы несколько дней, чтобы провести рефакторинг таким образом, чтобы это меня удовлетворило, но фактическое изменение заняло около половины дня. Проблема в том, что даже если мы « continue», нам все равно нужно освободить var1и var2. Я должен был добавить var3, и это заставило меня хотеть рвать, чтобы отражать выражения free ().

Я был относительно новым стажером в то время, но некоторое время назад я искал исходный код linux для развлечения, поэтому я спросил своего руководителя, могу ли я использовать оператор goto. Он сказал да, и я сделал это:

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);
}

Я думаю, что продолжение в порядке в лучшем случае, но для меня это как goto с невидимым лейблом. То же самое касается перерывов. Я бы все же предпочел продолжить или прервать, если, как это было в данном случае, это не заставит вас отражать изменения в нескольких местах.

И я должен также добавить, что это использование goto next;и next:этикетка являются неудовлетворительными для меня. Они просто лучше, чем зеркальное отражение free()и count++утверждения.

gotoПочти всегда они ошибаются, но нужно знать, когда они полезны.

Одна вещь, которую я не обсуждал, это обработка ошибок, которая была покрыта другими ответами.

Представление

Можно посмотреть на реализацию 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 */
}

Пожалуйста, исправьте меня, если я ошибаюсь, но я верю, что cont:метка и goto cont;утверждение предназначены для повышения производительности (они, конечно, не делают код более читабельным). Их можно заменить читабельным кодом, выполнив

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

пропустить разделители. Но чтобы быть максимально быстрым и избежать ненужных вызовов функций, они делают это таким образом.

Я прочитал статью Дейкстры и нахожу ее довольно эзотерической.

Google "Dijkstra Goto заявление считается вредным", потому что у меня недостаточно репутации, чтобы разместить более 2 ссылок.

Я видел, что это цитируется как причина не использовать goto, и чтение ничего не изменило, если принять во внимание, что я использую goto.

Приложение :

Я придумал аккуратное правило, думая обо всем этом о продолжениях и перерывах.

  • Если в цикле while у вас есть continue, тогда тело цикла while должно быть функцией, а continue - оператором return.
  • Если в цикле while у вас есть оператор break, то сам цикл while должен быть функцией, а разрыв должен стать оператором return.
  • Если у вас есть оба, то что-то может быть не так.

Это не всегда возможно из-за проблем с областью видимости, но я обнаружил, что это значительно упрощает анализ моего кода. Я заметил, что всякий раз, когда в цикле while происходит разрыв или продолжение, это вызывает у меня плохое предчувствие.

Филипп Карфин
источник
2
+1 но могу ли я не согласиться с одним пунктом? «Я думаю, что программистов нужно воспитывать, полагая, что goto - это зло». Может быть и так, но я впервые научился программировать на бейсике, с номерами строк и GOTO, без текстового редактора, в 1975 году. Я познакомился со структурированным программированием десять лет спустя, после чего мне потребовался один месяц, чтобы перестать использовать GOTO самостоятельно, без любое давление, чтобы остановить. Сегодня я пользуюсь GOTO несколько раз в год по разным причинам, но это мало что дает. Не будучи воспитанным, чтобы поверить, что GOTO - зло, я не причинил мне вреда, который я знаю, и это могло бы даже принести пользу. Это всего лишь я.
THB
1
Я думаю, что вы правы в этом. Меня воспитала идея, что GOTO не должны использоваться, и по чистой случайности я просматривал исходный код Linux в то время, когда работал над кодом, в котором эти функции имели несколько точек выхода с освобожденной памятью. Иначе я бы никогда не узнал об этих методах.
Филипп Карфин
1
@thb Кроме того, забавная история, я попросил своего руководителя в то время, как стажера, разрешить использовать GOTO, и я убедился, что объяснил ему, что собираюсь использовать их особым образом, как это используется в Ядро Linux, и он сказал: «Хорошо, это имеет смысл, а также, я не знал, что вы можете использовать GOTO в C».
Филипп Карфин
1
@ thb Я не знаю, хорошо ли переходить в циклы (вместо того, чтобы разрывать циклы), как этот ? Что ж, это плохой пример, но я обнаружил, что быстрая сортировка с операторами goto (пример 7a) в структурированном программировании Кнута с помощью go to Statement не очень понятна.
Yai0Phah
@ Yai0Phah Я объясню свою точку зрения, но мое объяснение не умаляет ваш прекрасный пример 7a! Я одобряю пример. Тем не менее, властные второкурсники любят читать лекции людям о гото. С 1985 года трудно найти практическое применение goto, которое вызывает значительные проблемы, в то время как можно найти безобидные goto, которые облегчают работу программиста. Goto настолько редко возникает в современном программировании, во всяком случае, что, когда оно действительно возникает, я советую, если вы хотите использовать его, то вам, вероятно, следует просто использовать его. Гото в порядке. Основная проблема с goto состоит в том, что некоторые считают, что устаревшее goto делает их умными.
THB
5

Лично я бы рефакторинг это больше так:

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);
    }
}

Это было бы более мотивированным, избегая глубокого вложения, чем избегая перехода, однако (IMO - более серьезная проблема с первым образцом кода), и, конечно, зависело бы от возможного выхода CleanupAfterError из области видимости (в этом случае «params» мог бы быть структурой, содержащей некоторую выделенную память, которую вам нужно освободить, ФАЙЛ *, который вам нужно закрыть или что-то еще).

Одно из основных преимуществ этого подхода заключается в том, что проще и чище выделить гипотетический будущий дополнительный шаг, скажем, между FTCF2 и FTCF3 (или удалить существующий текущий шаг), так что он лучше поддается обслуживанию (и человеку, который наследует мой код, не желая линчевать меня!) - в сторону, во вложенной версии этого нет.

Максимус Минимус
источник
1
Я не заявил об этом в своем вопросе, но возможно, что FTCF не имеют одинаковых параметров, что делает этот шаблон немного более сложным. Спасибо хоть.
Робз
3

Ознакомьтесь с рекомендациями по кодированию на языке C MISRA (Ассоциация по надежности программного обеспечения автомобильной промышленности), которые позволяют переходить по строгим критериям (что соответствует вашему примеру).

Там, где я работаю, будет написан один и тот же код - без необходимости - избегать ненужных религиозных дискуссий о них - большой плюс в любой фирме программного обеспечения.

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:
} 

или для "goto in drag" - что-то еще более хитрое, чем goto, но обходится без "Goto Ever !!!" лагерь) "Конечно, все должно быть в порядке, не использовать Гото" ....

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

Если функции имеют одинаковый тип параметра, поместите их в таблицу и используйте цикл -

mattnz
источник
2
Нынешние руководящие принципы MISRA-C: 2004 не позволяют использовать goto в любой форме (см. Правило 14.4). Обратите внимание, что комитет MISRA всегда смущался по этому поводу, они не знают, на какой ноге стоять. Во-первых, они безоговорочно запретили использование goto, continue и т. Д. Но в проекте для предстоящего MISRA 2011 они хотят разрешить их снова. В качестве примечания, пожалуйста, обратите внимание, что MISRA запрещает присваивание внутри операторов if по очень веским причинам, поскольку это гораздо более опасно, чем любое использование goto.
1
С аналитической точки зрения, добавление флага в программу эквивалентно дублированию всего кода кода, где находится флаг в области видимости, когда каждая if(flag)в одной копии принимает ветвь «если», а каждая соответствующая инструкция в другой копии принимает « еще». Действия, которые устанавливают и сбрасывают флаг, на самом деле являются «gotos», которые перемещаются между этими двумя версиями кода. Бывают случаи, когда использование флагов чище, чем любая альтернатива, но добавление флага для сохранения одной gotoцели не является хорошим компромиссом.
суперкат
1

Я также использую, gotoесли альтернативный do/while/continue/breakхакер будет менее читабельным.

gotoПреимущество в том, что у их целей есть имя, и они читают goto something;. Это может быть более удобным для чтения , чем breakили , continueесли вы на самом деле не остановить или продолжить что - то.

AIB
источник
4
В любом месте внутри do ... while(0)или другой конструкции, которая не является реальным циклом, а является попыткой предотвратить использование a goto.
AIB
1
Ах, спасибо, я не знала эту конкретную марку "Почему, черт возьми, кто-то это сделал ?!" пока конструирует.
Бенджамин Kloster
2
Обычно хакер do / while / continue / break становится нечитаемым только тогда, когда модуль, в котором он находится, слишком длинен в первую очередь.
Джон Р. Штром
2
Я не могу найти в этом ничего, чтобы оправдать использование goto. Перерыв и продолжение имеют очевидное следствие. Гото ... где? Где этикетка? Перерыв и идти, чтобы сказать вам точно, где следующий шаг и его поблизости.
Рог
1
Конечно, метка должна быть видна изнутри петли. Я согласен с частью длины комментария @John R. Strohm. И ваша точка зрения, переведенная как хакерская петля, становится такой: «Вырваться из чего? Это не петля!». В любом случае, это становится тем, чего боялся ФП, поэтому я отказываюсь от обсуждения.
AIB
-1
for (int y=0; y<height; ++y) {
    for (int x=0; x<width; ++x) {
        if (find(x, y)) goto found;
    }
}
found:
AIB
источник
Если есть только одна петля, breakработает точно так же goto, хотя и не несет стигмы.
9000
6
-1: во-первых, x и y находятся вне области видимости :, так что это вам не поможет. Во-вторых, с написанным кодом обнаружен тот факт, что вы пришли: это не значит, что вы нашли то, что искали.
Джон Р. Штром
Это потому, что это наименьший пример, который я могу вспомнить в случае разрыва множества циклов. Пожалуйста, не стесняйтесь редактировать его для лучшего ярлыка или готовой проверки.
AIB
1
Но также имейте в виду, что функции C не обязательно не имеют побочных эффектов.
AIB
1
@ JohnR.Strohm Это не имеет большого смысла ... Метка «найдено» используется для прерывания цикла, а не для проверки переменных. Если бы я хотел проверить переменные, я мог бы сделать что-то вроде этого: for (int y = 0; y <высота; ++ y) {for (int x = 0; x <width; ++ x) {if (find ( x, y)) {doSomeThingWith (x, y); гото найдено; }}} найдено:
YoYoYonnY
-1

Всегда будут лагеря, которые говорят, что один путь приемлем, а другой нет. Компании, на которые я работал, нахмурились или категорически не одобряли использование GoTo. Лично я не могу думать ни о каком времени, когда использовал один, но это не значит, что они плохие , просто еще один способ сделать что-то.

В Си я обычно делаю следующее:

  • Проверьте условия, которые могут помешать обработке (неверные данные и т. Д.) И «возврату»
  • Выполните все шаги, которые требуют выделения ресурсов (например, mallocs)
  • Выполните обработку, где несколько шагов проверяют на успешность
  • Освободить любые ресурсы, если они успешно распределены
  • Вернуть любые результаты

Для обработки, используя ваш пример goto, я бы сделал это:

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

Здесь нет вложенности, и внутри предложений if вы можете делать любые сообщения об ошибках, если на шаге возникла ошибка. Таким образом, это не должно быть «хуже», чем метод с использованием gotos.

Я еще не сталкивался со случаем, когда у кого-то есть gotos, который нельзя сделать с помощью другого метода и который так же читабелен / понятен, и это ключ, ИМХО.

РСМ
источник