Примеры хороших 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
FlySwat
источник
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.

user25306
источник
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;
}
FlySwat
источник
13
Он добавляет ненужные уровни отступов, которые могут стать очень опасными, если у вас есть большое количество возможных режимов отказа.
Адам Розенфилд,
6
Я бы взял отступы над goto в любое время.
FlySwat,
13
Кроме того, в нем гораздо больше строк кода, в одном из случаев имеется избыточный вызов функции, и гораздо сложнее правильно добавить doD.
Патрик,
6
Я согласен с Патриком - я видел, как эта идиома использовалась с несколькими условиями, вложенными на глубину примерно 8 уровней. Очень противно. И очень подвержен ошибкам, особенно при попытке добавить что-то новое в микс.
Майкл Бёрр,
11
Этот код просто кричит: «У меня будет ошибка через несколько следующих недель», кто-то забудет что-то исправить. Вам нужно, чтобы все ваши отмены находились в одном месте. Это похоже на «наконец» в родных объектно-ориентированных языках.
Илья