Примеры хороших gotos на C или C ++ [закрыто]


79

В этой теме мы рассмотрим примеры правильного использования gotoC или C ++. Он вдохновлен ответом, за который люди проголосовали, потому что думали, что я шучу.

Резюме (ярлык изменен с оригинала, чтобы прояснить намерения):

infinite_loop:

    // code goes here

goto infinite_loop;

Почему лучше альтернатив:

  • Это специфично. gotoэто языковая конструкция, которая вызывает безусловный переход. Альтернативы зависят от использования структур, поддерживающих условные переходы, с вырожденным условием Always-True.
  • Этикетка документирует намерение без дополнительных комментариев.
  • Читателю не нужно сканировать промежуточный код на ранних этапах break(хотя для беспринципного хакера все еще возможно смоделировать continueс ранним goto).

Правила:

  • Представьте, что готофобы не победили. Понятно, что приведенное выше нельзя использовать в реальном коде, потому что это противоречит устоявшейся идиоме.
  • Предположим, что все мы слышали о том, что Goto считается вредным, и знаем, что goto можно использовать для написания кода спагетти.
  • Если вы не согласны с примером, критикуйте его только по техническим причинам («Потому что людям не нравится goto» - это не техническая причина).

Посмотрим, сможем ли мы говорить об этом, как взрослые.

редактировать

Этот вопрос кажется законченным. Это дало несколько качественных ответов. Спасибо всем, особенно тем, кто серьезно отнесся к моему примеру с петлей. Большинство скептиков было обеспокоено отсутствием возможности блокировки. Как отметил @quinmars в комментарии, вы всегда можете поместить фигурные скобки вокруг тела цикла. Я мимоходом отмечу это for(;;)и while(true)не даю вам брекеты бесплатно (их пропуск может вызвать досадные ошибки). Во всяком случае, я не буду тратить больше ваш мозг власти на этой мелочи - я могу жить с безвредным и идиоматическим for(;;)и while(true)(точно так же , если я хочу , чтобы сохранить свою работу).

Принимая во внимание другие ответы, я вижу, что многие люди считают gotoчто-то, что вам всегда нужно переписывать по-другому. Конечно, вы можете избежать a goto, введя цикл, дополнительный флаг, стек вложенных ifs или что-то еще, но почему бы не подумать, gotoможет ли это лучший инструмент для работы? Другими словами, на сколько уродства готовы вынести люди, чтобы избежать использования встроенной языковой функции по прямому назначению? Я считаю, что даже добавление флага - это слишком высокая цена. Мне нравится, что мои переменные представляют объекты в области проблем или решений. «Исключительно для того, чтобы избежать» - gotoэто не значит.

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


11
Я не понимаю, почему готофобы просто не предписывают "#define goto report_to_your_supervisor_for_re_education_through_labour" в верхней части включаемого файла проекта. Если это всегда неправильно, сделайте это невозможным. В противном случае, иногда это правильно ...
Стив Джессоп

14
"нельзя использовать в реальном коде"? Я использую его в «реальном» коде каждый раз, когда это лучший инструмент для работы. «Установленная идиома» - хороший эвфемизм для «слепого догматизма».
Дэн Молдинг,

7
Я согласен с тем, что "goto" может быть полезным (ниже приведены отличные примеры), но я не согласен с вашим конкретным примером. Строка с надписью «goto infinite_loop» звучит так, как будто она означает «перейти к той части кода, где мы собираемся начать цикл навсегда», как в «initialize (); set_things_up (); goto infinite_loop;» когда то, что вы действительно хотите передать, - это «начать следующую итерацию цикла, в котором мы уже находимся», что совершенно другое. Если вы пытаетесь создать цикл, а в вашем языке есть конструкции, разработанные специально для цикла, используйте их для ясности. while (true) {foo ()} довольно недвусмысленно.
Джош

6
Одним из основного недостатка вашего примера является то , что это не ясно , каким Отерло место в коде может принять решение перейти к этой метке. «Обычный» бесконечный цикл ( for (;;)) не имеет неожиданных точек входа.
jalf 05

3
@ Дьёрдь: да, но это не отправная точка.
jalf 05

Ответы:


87

Вот одна уловка, которую я слышал от людей. Однако я никогда не видел его в дикой природе. И это применимо только к C, потому что в C ++ есть RAII, чтобы сделать это более идиоматично.

void foo()
{
    if (!doA())
        goto exit;
    if (!doB())
        goto cleanupA;
    if (!doC())
        goto cleanupB;

    /* everything has succeeded */
    return;

cleanupB:
    undoB();
cleanupA:
    undoA();
exit:
    return;
}

2
Что в этом плохого? (кроме того факта, что комментарии не понимают символы новой строки) void foo () {if (doA ()) {if (doB ()) {if (doC ()) {/ * все прошло успешно * / return; } undoB (); } undoA (); } возвращение; }
Artelius

6
Дополнительные блоки вызывают ненужные отступы, которые трудно читать. Он также не работает, если одно из условий находится внутри цикла.
Адам Розенфилд

26
Вы можете увидеть много такого кода в большинстве низкоуровневых вещей Unix (например, в ядре Linux). В C это лучшая идиома для восстановления ошибок ИМХО.
Дэвид Курнапо,

8
Как сказал Курнап, ядро ​​Linux все время использует этот стиль .
CesarB,

8
Не только ядро ​​Linux, посмотрите образцы драйверов Windows от Microsoft, и вы найдете тот же шаблон. В общем, это способ обработки исключений на языке C и очень полезный :). Я обычно предпочитаю только 1 этикетку, а в некоторых случаях 2, 3 можно избежать в 99% случаев.
Илья

85

Классическая потребность в GOTO в C следующая

for ...
  for ...
    if(breakout_condition) 
      goto final;

final:

Нет простого способа выйти из вложенных циклов без перехода.


49
Чаще всего, когда возникает такая необходимость, я могу использовать возврат. Но для этого нужно писать небольшие функции.
Дариус Бэкон,

7
Я определенно согласен с Дариусом - преобразовать его в функцию и вместо этого вернуть!
metao

9
@metao: Бьярн Страуструп не согласен. В его книге "Язык программирования C ++" это как раз пример "правильного использования" goto.
paercebal

19
@ Адам Розенфилд: Вся проблема goto, которую подчеркивает Дейкстра, заключается в том, что структурное программирование предлагает более понятные конструкции. Усложнение чтения кода во избежание перехода к goto доказывает, что автор не понял этого эссе ...
Стив Джессоп,

5
@Steve Jessop: Люди не только неправильно поняли эссе, но и большинство сектантов, которым "не идти", не понимают исторического контекста, в котором оно было написано.
ТОЛЬКО МОЕ ПРАВИЛЬНОЕ МНЕНИЕ

32

Вот мой нелепый пример (от Stevens APITUE) для системных вызовов Unix, которые могут быть прерваны сигналом.

restart:
    if (system_call() == -1) {
        if (errno == EINTR) goto restart;

        // handle real errors
    }

Альтернатива - вырожденный цикл. Эта версия читается как английский «если системный вызов был прерван сигналом, перезапустите его».


3
этот способ используется, например, у планировщика linux есть такой переход, но я бы сказал, что очень мало случаев, когда обратный переход приемлем и в целом его следует избегать.
Илья

8
@Amarghosh, continueэто просто gotoмаска.
jball 03

2
@jball ... хм ... ради аргумента, ага; вы можете сделать хорошо читаемый код с помощью goto и спагетти-кода с помощью continue .. В конечном итоге это зависит от человека, который пишет код. Дело в том, что с goto легче заблудиться, чем с continue. И новички обычно используют первый молоток для решения каждой проблемы.
Amarghosh

2
@ Амаргош. Ваш «улучшенный» код даже не эквивалентен оригиналу - в случае успеха он повторяется бесконечно.
fizzer 04

3
@fizzer oopsie .. ему понадобится два else breaks (по одному на каждое if), чтобы сделать его эквивалентным ... что я могу сказать ... gotoили нет, ваш код настолько хорош, как вы :(
Amarghosh

14

Если устройству Даффа не требуется goto, то и вам не нужно! ;)

void dsend(int count) {
    int n;
    if (!count) return;
    n = (count + 7) / 8;
    switch (count % 8) {
      case 0: do { puts("case 0");
      case 7:      puts("case 7");
      case 6:      puts("case 6");
      case 5:      puts("case 5");
      case 4:      puts("case 4");
      case 3:      puts("case 3");
      case 2:      puts("case 2");
      case 1:      puts("case 1");
                 } while (--n > 0);
    }
}

Код выше из Википедии записи .


2
Да ладно, ребята, если вы собираетесь проголосовать против, короткий комментарий о том, почему было бы здорово. :)
Митч Уит

10
Это пример логической ошибки, известной также как «апелляция к авторитету». Проверьте это в Википедии.
Маркус Грип,

11
юмор здесь часто недооценивают ...
Стивен А. Лоу

8
устройство даффа делает пиво?
Стивен А. Лоу

6
этот может сделать 8 за раз ;-)
Джаспер Беккерс

14

Кнут написал статью «Структурированное программирование с операторами GOTO», вы можете получить ее, например, здесь . Вы найдете там много примеров.


Эта бумага выглядит глубокой. Я все же прочту это. Спасибо
fizzer

2
Эта бумага настолько устарела, что это даже не смешно. Предположения Кнута здесь просто не действуют.
Конрад Рудольф

3
Какие предположения? Его примеры сегодня так же реальны для процедурного языка, как C (он приводит их в некотором псевдокоде), как и в то время.
zvrba

8
Я разочарован, что вы не написали :: Вы можете ПЕРЕЙТИ "сюда", чтобы получить это, каламбур для меня было бы невыносимым, чтобы не сделать!
RhysW 08

12

Очень распространен.

do_stuff(thingy) {
    lock(thingy);

    foo;
    if (foo failed) {
        status = -EFOO;
        goto OUT;
    }

    bar;
    if (bar failed) {
        status = -EBAR;
        goto OUT;
    }

    do_stuff_to(thingy);

OUT:
    unlock(thingy);
    return status;
}

Единственный случай, который я когда-либо использовал, goto- это прыгать вперед, обычно из блоков, а не в блоки. Это позволяет избежать злоупотреблений do{}while(0)и других конструкций, которые увеличивают вложенность, сохраняя при этом читаемый структурированный код.


5
Я думаю, что это типичный способ обработки ошибок C. Я не вижу, чтобы он был заменен красиво, лучше читается в любом другом случае С уважением
Фридрих

Почему бы и нет ... do_stuff(thingy) { RAIILockerObject(thingy); ..... /* -->*/ } /* <---*/ :)
Петър Петров

1
@ ПетърПетров Это шаблон C ++, не имеющий аналогов в C.
ephemient

Или на даже более структурированном языке, чем C ++, try { lock();..code.. } finally { unlock(); }но да, у C нет эквивалента.
Blindy

12

Я ничего не имею против gotos в целом, но я могу придумать несколько причин, по которым вы не захотите использовать их для цикла, как вы упомянули:

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

1
это inf_loop: {/ * тело цикла * /} goto inf_loop; лучше? :)
quinmars

2
Пункты 1-4 сомнительны для этого примера даже без фигурных скобок. 1 Это бесконечный цикл. 2 Слишком расплывчато, чтобы адресовать. 3 Попытка скрыть переменную с тем же именем во включающей области видимости - плохая практика. 4. Обратный переход не может пропустить объявление.
fizzer

1-4, как и во всех бесконечных циклах, в середине обычно есть какое-то условие разрыва. Так что они применимы. Re a bakward goto не может пропустить объявление ... не все goto перевернуты ...
Брайан Р. Бонди

7

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

если (! openDataFile ())
  перейти к выходу;

если (! getDataFromFile ())
  goto closeFileAndQuit;

если (! allocateSomeResources)
  goto freeResourcesAndQuit;

// Здесь можно еще поработать ....

freeResourcesAndQuit:
   // бесплатные ресурсы
closeFileAndQuit:
   // закрываем файл
уволиться:
   // уволиться!

Я хотел бы заменить это с набором функций: freeResourcesCloseFileQuit, closeFileQuitи quit.
RCIX

4
Если вы создали эти 3 дополнительные функции, вам нужно будет передать указатели / дескрипторы ресурсов и файлов, которые нужно освободить / закрыть. Вы, вероятно, заставили бы freeResourcesCloseFileQuit () вызвать closeFileQuit (), который, в свою очередь, вызвал бы quit (). Теперь у вас есть 4 тесно связанных функции, которые нужно поддерживать, и 3 из них, вероятно, будут вызываться не более одного раза: из одной функции выше. Если вы настаиваете на том, чтобы избегать goto, IMO, вложенные блоки if () имеют меньше накладных расходов и их легче читать и поддерживать. Что вы получаете с 3 дополнительными функциями?
Адам Лисс

7

@ fizzer.myopenid.com: размещенный вами фрагмент кода эквивалентен следующему:

    while (system_call() == -1)
    {
        if (errno != EINTR)
        {
            // handle real errors

            break;
        }
    }

Я определенно предпочитаю эту форму.


1
Хорошо, я знаю, что оператор break - это просто более приемлемая форма goto ..
Митч Уит,

10
На мой взгляд, это сбивает с толку. Это цикл, который вы не ожидаете войти в обычный путь выполнения, поэтому логика кажется обратной. Тем не менее, это суть мнения.
fizzer

1
Назад? Он начинается, он продолжается, он проваливается. Думаю, эта форма более явно выражает свои намерения.
Митч Уит

8
Я согласен с fizzer в том, что goto дает более четкое представление о значении условия.
Маркус Грип,

4
Чем легче читать этот фрагмент, чем fizzer?
erikkallen

6

Несмотря на то, что со временем я возненавидел этот шаблон, он встроен в программирование COM.

#define IfFailGo(x) {hr = (x); if (FAILED(hr)) goto Error}
...
HRESULT SomeMethod(IFoo* pFoo) {
  HRESULT hr = S_OK;
  IfFailGo( pFoo->PerformAction() );
  IfFailGo( pFoo->SomeOtherAction() );
Error:
  return hr;
}

5

Вот пример хорошего goto:

// No Code

2
эээ, нет gotos? (неудачная попытка смешно: d)
moogs

9
Это бесполезно
Джозеф

Я думал, это было забавно. Отлично сработано. +1
Фантастический мистер Фокс

@FlySwat FYI: Нашел это в очереди на проверку низкого качества. Рассмотрите возможность редактирования.
Пол

1

Я видел, как goto используется правильно, но ситуации обычно уродливые. Это только тогда, когда использование самого gotoсебя намного меньше, чем у оригинала. @Johnathon Holland проблема в том, что ваша версия менее ясна. люди, кажется, боятся локальных переменных:

void foo()
{
    bool doAsuccess = doA();
    bool doBsuccess = doAsuccess && doB();
    bool doCsuccess = doBsuccess && doC();

    if (!doCsuccess)
    {
        if (doBsuccess)
            undoB();
        if (doAsuccess)
            undoA();
    }
}

И я предпочитаю такие петли, но некоторые предпочитают while(true).

for (;;)
{
    //code goes here
}

2
Никогда и никогда не сравнивайте логическое значение с истиной или ложью. Это уже логическое значение; просто используйте "doBsuccess = doAsuccess && doB ();" и вместо этого "if (! doCsuccess) {}". Это проще и защищает вас от умных программистов, которые пишут такие вещи, как «#define true 0», потому что, ну, потому что они могут. Он также защищает вас от программистов, которые смешивают целые и логические значения и затем предполагают, что любое ненулевое значение истинно.
Адам Лисс

Спасибо, и точка == trueснята. В любом случае это ближе к моему настоящему стилю. :)
Чарльз Битти

0

Меня беспокоит то, что вы теряете область видимости блока; любые локальные переменные, объявленные между gotos, остаются в силе, если цикл когда-либо прерывается. (Возможно, вы предполагаете, что цикл выполняется вечно; однако я не думаю, что это то, что задавал исходный автор вопроса.)

Проблема области видимости больше связана с C ++, поскольку некоторые объекты могут зависеть от того, как их dtor вызывается в соответствующее время.

Для меня лучшая причина использовать goto - это во время многоэтапного процесса инициализации, когда жизненно важно, чтобы все inits были отменены в случае сбоя одного из них, а-ля:

if(!foo_init())
  goto bye;

if(!bar_init())
  goto foo_bye;

if(!xyzzy_init())
  goto bar_bye;

return TRUE;

bar_bye:
 bar_terminate();

foo_bye:
  foo_terminate();

bye:
  return FALSE;

0

Я сам не использую goto, однако однажды я работал с человеком, который использовал бы их в определенных случаях. Если я правильно помню, его обоснование было связано с проблемами производительности - у него также были определенные правила, как это сделать . Всегда в одной и той же функции, а метка всегда была НИЖЕ оператора goto.


4
Дополнительные данные: обратите внимание, что вы не можете использовать goto для выхода за пределы функции, и что в C ++ запрещено обходить создание объекта с помощью goto
paercebal

0
#include <stdio.h>
#include <string.h>

int main()
{
    char name[64];
    char url[80]; /*The final url name with http://www..com*/
    char *pName;
    int x;

    pName = name;

    INPUT:
    printf("\nWrite the name of a web page (Without www, http, .com) ");
    gets(name);

    for(x=0;x<=(strlen(name));x++)
        if(*(pName+0) == '\0' || *(pName+x) == ' ')
        {
            printf("Name blank or with spaces!");
            getch();
            system("cls");
            goto INPUT;
        }

    strcpy(url,"http://www.");
    strcat(url,name);
    strcat(url,".com");

    printf("%s",url);
    return(0);
}

-1

@ Грег:

Почему бы не сделать ваш пример таким:

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}

13
Он добавляет ненужные уровни отступов, которые могут стать очень опасными, если у вас есть большое количество возможных режимов отказа.
Адам Розенфилд,

6
Я бы взял отступы над goto в любое время.
FlySwat,

13
Кроме того, в нем гораздо больше строк кода, в одном из случаев имеется избыточный вызов функции, и гораздо сложнее правильно добавить doD.
Патрик,

6
Я согласен с Патриком - я видел, как эта идиома использовалась с несколькими условиями, вложенными на глубину примерно 8 уровней. Очень противно. И очень подвержен ошибкам, особенно при попытке добавить что-то новое в микс.
Майкл Бёрр,

11
Этот код просто кричит: «У меня будет ошибка через несколько следующих недель», кто-то забудет что-то исправить. Вам нужно, чтобы все ваши отмены находились в одном месте. Это похоже на «наконец» в родных объектно-ориентированных языках.
Илья
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.