В этой теме мы рассмотрим примеры правильного использования goto в C или C++. Он вдохновлен ответ, за который люди проголосовали, потому что думали, что я шучу.
Резюме (ярлык изменен с оригинала, чтобы прояснить намерения):
infinite_loop:
// code goes here
goto infinite_loop;
Почему лучше альтернатив:
goto - это
языковая конструкция, которая вызывает
безусловная ветвь. Альтернативы
зависят от использования структур
поддерживающие условные ветви,
с вырожденным всегда верным
условие.break
(хотя все еще возможно
беспринципный хакер симулировать
continue с ранним goto).Правила:
Посмотрим, сможем ли мы говорить об этом, как взрослые.
Редактировать
Этот вопрос кажется законченным. Это дало несколько качественных ответов. Спасибо всем,
особенно те, кто серьезно относился к моему примеру с петлей. Большинство скептиков были обеспокоены
из-за отсутствия блочного прицела. Как отметил @quinmars в комментарии, вы всегда можете поставить скобки вокруг
тело петли. Замечу попутно, что for(;;) и while(true) не дают вам брекетов.
бесплатно (и их пропуск может вызвать досадные ошибки). В любом случае, я больше не буду тратить впустую
Я могу жить с безобидными и идиоматическими for(;;) и while(true) (с тем же успехом, если я хочу сохранить свою работу).
Принимая во внимание другие отзывы, я вижу, что многие люди рассматривают goto как то, что вы всегда
придется по другому переписать. Конечно, вы можете избежать появления goto, введя цикл,
дополнительный флаг, стек вложенных if или что-то еще, но почему бы не подумать, является ли goto
возможно лучший инструмент для работы? Другими словами, на сколько уродства готовы вынести люди, чтобы избежать использования встроенной языковой функции по прямому назначению? Я считаю, что
даже установка флага - слишком высокая цена. Мне нравится, когда мои переменные представляют вещи в
области проблемы или решения. «Исключительно для того, чтобы избежать goto», это не сокращает.
Я приму первый ответ, который дал шаблон C для перехода к блоку очистки. ИМО, это самый веский аргумент в пользу goto из всех опубликованных ответов, конечно
если вы измеряете это искажениями, через которые должен пройти ненавистник, чтобы избежать этого.
"нельзя использовать в реальном коде"? Я использую его в «реальном» коде каждый раз, когда это лучший инструмент для работы. «Установленная идиома» - хороший эвфемизм для «слепого догматизма».
Я согласен с тем, что «goto» может быть полезным (ниже приведены отличные примеры), но я не согласен с вашим конкретным примером. Строка с надписью «goto infinite_loop» звучит так, как будто она означает «перейти к той части кода, где мы собираемся начать цикл навсегда», как в «initialize (); set_things_up (); goto infinite_loop;» когда на самом деле вы имеете в виду «начать следующую итерацию цикла, в котором мы уже находимся», что совершенно другое. Если вы пытаетесь создать цикл, а в вашем языке есть конструкции, разработанные специально для цикла, используйте их для ясности. while (true) {foo ()} довольно недвусмысленно.
Одним из основных недостатков вашего примера является то, что неясно, какое место охтер в коде может решить перейти к этой метке. «Обычный» бесконечный цикл (for (;;)) не имеет неожиданных точек входа.
@ Дьёрдь: да, но это не отправная точка.
См. Статью Кнута «Структурированное программирование с использованием операторов перехода».
Другой способ, если кто-то хочет убить вас из-за goto, попробуйте использовать do{}while(0);, как в этой конструкции do{ if ( 1 > 0 ){ break;} } while(0);





Вот пример хорошего goto:
// No Code
эээ, нет gotos? (неудачная попытка смешного: d)
Это не очень полезно
Я подумал, что это было забавно. Отличная работа. +1
@FlySwat FYI: Нашел это в очереди на проверку низкого качества. Рассмотрите возможность редактирования.
Я ничего не имею против gotos в целом, но я могу придумать несколько причин, по которым вы не захотите использовать их для цикла, как вы упомянули:
это inf_loop: {/ * тело цикла * /} goto inf_loop; лучше? :)
Пункты 1-4 являются сомнительными для этого примера даже без фигурных скобок. 1 Это бесконечный цикл. 2 Слишком расплывчато, чтобы адресовать. 3 Пытаться скрыть переменную с тем же именем во включающей области видимости - плохая практика. 4. Обратный переход не может пропустить объявление.
1-4, как и во всех бесконечных циклах, в середине обычно есть какое-то условие разрыва. Значит они применимы. Re a bakward goto не может пропустить объявление ... не все goto являются обратными ...
Вот мой нелепый пример (от Stevens APITUE) для системных вызовов Unix, которые могут быть прерваны сигналом.
restart:
if (system_call() == -1) {
if (errno == EINTR) goto restart;
// handle real errors
}
Альтернатива - вырожденный цикл. Эта версия читается как английская «если системный вызов был прерван сигналом, перезапустите его».
этот способ используется, например, у планировщика Linux есть такой переход, но я бы сказал, что очень мало случаев, когда обратный переход приемлем, и его в целом следует избегать.
while(1){if (system_call() == -1){if (errno == EINTR)continue;//handle real errors}}@Amarghosh, continue - это просто goto в маске.
@jball и более безопасен / удобен (без спагетти-кода), чем goto
@jball ... хм ... ради аргумента, ага; вы можете сделать хорошо читаемый код с помощью goto и спагетти-кода с помощью continue .. В конечном итоге это зависит от человека, который пишет код. Дело в том, что с goto легче заблудиться, чем с continue. И новички обычно используют первый молоток для решения каждой проблемы.
@Amarghosh. Ваш «улучшенный» код даже не эквивалентен оригиналу - в случае успеха он повторяется бесконечно.
@fizzer oopsie ... ему понадобится два else break (по одному на каждое if), чтобы сделать его эквивалентным ... что я могу сказать ... goto или нет, ваш код настолько хорош, насколько хорош вы :(
@Amarghosh не нужно ни goto, ни else. while (system_call () == -1) if (errno == EINTR) continue; / * обрабатываем реальные ошибки * / break; }
Для этого уже существует макрос TEMP_FAILURE_RETRY (определенный в unistd.h). Реализовано без использования goto: while (__result == -1L && errno == EINTR);.
Если устройству Даффа не требуется 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);
}
}
код выше из Википедии Вход.
Да ладно, ребята, если вы собираетесь проголосовать против, короткий комментарий о том, почему было бы здорово. :)
Это пример логической ошибки, известной также как «обращение к авторитету». Проверьте это в Википедии.
юмор здесь часто недооценивают ...
Я бы не стал нанимать программиста без чувства юмора.
устройство даффа делает пиво?
это может сделать 8 за раз ;-)
Однако не вставляйте это устройство в свой код - на современных машинах оно обычно медленнее, чем memcpy ()
Митч Уит: наверное, потому, что кто-то отнесся к этому серьезно.
Устройство Даффа является доказательством того, что goto не вреден: он не использует goto и, тем не менее, настолько похож на спагетти, который человечество редко видит. И если это единственный способ предотвратить переход, я бы предпочел последний. Итак, +0,5 за упоминание устройства Даффа, но -1,1 за его использование в качестве аргумента против goto.
@glglgl: инструкция switch - это версия C инструкции HP Basic GOTO I OF xx,yy,zz. Перед целями стоит слово case, но они ограничены по объему, но switch в значительной степени является структурой, основанной на goto, о чем свидетельствуют ее правила обхода.
Вот одна уловка, которую я слышал от людей. Хотя я никогда не видел его в дикой природе. И это применимо только к 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;
}
Что в этом плохого? (кроме того факта, что комментарии не понимают символы новой строки) void foo () {if (doA ()) {if (doB ()) {if (doC ()) {/ * все прошло успешно * / return; } undoB (); } undoA (); } возвращаться; }
Дополнительные блоки вызывают ненужные отступы, которые трудно читать. Он также не работает, если одно из условий находится внутри цикла.
Вы можете увидеть много такого кода в большинстве низкоуровневых вещей Unix (например, в ядре Linux). В C это лучшая идиома для восстановления ошибок ИМХО.
Как упоминал Курнап, ядро Linux использует этот стиль все время.
Не только ядро Linux, посмотрите образцы драйверов Windows от Microsoft, и вы найдете тот же шаблон. В общем, это способ обработки исключений на языке C, и он очень полезен :). Я обычно предпочитаю только 1 этикетку, а в некоторых случаях 2, 3 можно избежать в 99% случаев.
У меня много раз возникали проблемы на работе из-за использования этого «паттерна», но я все же думаю, что это может быть самый ясный подход к некоторым ситуациям ...
У меня также были проблемы с использованием нескольких возвратов (также полезно в некоторых ситуациях для предотвращения бесконечных уровней отступов и неразборчивых извилистых путей кода).
Почему это лучше, чем просто вызвать undoB () напрямую?
@Bob Старая ветка, которую я знаю, но это потому, что тогда вам нужно было бы сделать undoB(); undoA(); /*goto exit; or return;*/, а если бы у вас был doD(), вам пришлось бы undoC(), а также все элементы, отмененные в неудачном doC().
Именно так goto работает в bat-файле MS-DOS.
Я согласен с Грегом, C++ имеет RAII, поэтому вам никогда не понадобится такой код, но для кода C я бы предпочел код @Artelius без goto.
Одно хорошее место для использования goto - это процедура, которая может прерваться в нескольких точках, каждая из которых требует различных уровней очистки. Gotophobes всегда может заменить gotos структурированным кодом и серией тестов, но я думаю, что это более просто, потому что он устраняет чрезмерные отступы:
if (!openDataFile()) goto quit; if (!getDataFromFile()) goto closeFileAndQuit; if (!allocateSomeResources) goto freeResourcesAndQuit; // Do more work here.... freeResourcesAndQuit: // free resources closeFileAndQuit: // close file quit: // quit!
Я бы заменил это набором функций: freeResourcesCloseFileQuit, closeFileQuit и quit.
Если вы создали эти 3 дополнительные функции, вам нужно будет передать указатели / дескрипторы ресурсов и файлов, которые нужно освободить / закрыть. Вы, вероятно, заставили бы freeResourcesCloseFileQuit () вызвать closeFileQuit (), который, в свою очередь, вызвал бы quit (). Теперь у вас есть 4 тесно связанных функции, которые нужно поддерживать, и 3 из них, вероятно, будут вызываться не более одного раза: из единственной функции, описанной выше. Если вы настаиваете на том, чтобы избегать goto, IMO, вложенные блоки if () имеют меньше накладных расходов и их легче читать и поддерживать. Что вы получаете с 3 дополнительными функциями?
Меня беспокоит то, что вы теряете область видимости блока; любые локальные переменные, объявленные между 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;
@ Грег:
Почему бы не сделать ваш пример таким:
void foo()
{
if (doA())
{
if (doB())
{
if (!doC())
{
UndoA();
UndoB();
}
}
else
{
UndoA();
}
}
return;
}
Он добавляет ненужные уровни отступов, которые могут стать очень опасными, если у вас есть большое количество возможных режимов отказа.
Я бы взял отступы над goto в любое время.
Кроме того, в нем гораздо больше строк кода, в одном из случаев имеется избыточный вызов функции, и гораздо сложнее правильно добавить doD.
Я согласен с Патриком - я видел, как эта идиома использовалась с несколькими условиями, вложенными на глубину примерно 8 уровней. Очень противно. И очень подвержен ошибкам, особенно при попытке добавить что-то новое в микс.
Этот код просто кричит: «У меня будет ошибка через несколько следующих недель», кто-то забудет что-то отменить. Вам нужно, чтобы все ваши отмены находились в одном месте. Это похоже на «наконец» в родных объектно-ориентированных языках.
Мне не нравится повторение «UndoA ()» с копированием и вставкой. Код C'n'P примерно так же плох, как goto, и для меня в этом случае он проигрывает, потому что он более подробный. Также тот факт, что B делается после A, а также отменяется после A, заставляет мои зубы скручиваться, но это не часть шаблона здесь.
Вы можете переписать это с меньшим вложением, выполнив if (! DoA ()) {return; } если (! doB ()) {UndoA (); возвращаться; } и так далее. У вас по-прежнему будут повторяющиеся вызовы функций, но, по крайней мере, код будет читабельным.
Почему вы не могли использовать здесь оператор case?
@AlexandreC. А если под C работать, RAII отпадает и у вас goto >> эта хрень.
@ Остин: Это тоже возможно при определенных обстоятельствах. Правило должно быть таким: «Избегайте goto, если есть реальные альтернативы, но используйте его, когда это необходимо».
И сегодня я нашел отличную иллюстрацию того, как выглядит код пирамиды. msdn.microsoft.com/en-us/library/windows/desktop/…
Я предполагаю, что вопрос был задуман в саркастической манере, потому что код очень ясно показывает полезность goto или, по крайней мере, демонстрирует, что вы можете написать код хуже, чем использование goto.
@ fizzer.myopenid.com: размещенный вами фрагмент кода эквивалентен следующему:
while (system_call() == -1)
{
if (errno != EINTR)
{
// handle real errors
break;
}
}
Я определенно предпочитаю эту форму.
Хорошо, я знаю, что оператор break - это просто более приемлемая форма goto.
На мой взгляд, это сбивает с толку. Это цикл, который вы не ожидаете войти в обычный путь выполнения, поэтому логика кажется обратной. Тем не менее, это основа мнения.
Назад? Он начинается, он продолжается, он проваливается. Думаю, эта форма более явно выражает свои намерения.
Я согласен с fizzer здесь, что goto дает более четкое представление о значении условия.
Хорошо, нам придется не соглашаться по этому поводу.
+1, потому что версия while более ясна и избегает использования goto, которые имеют некоторые ограничения на прохождение через конструкцию объекта.
Чем легче читать этот фрагмент, чем fizzer?
Я просто столкнулся с чем-то похожим в своем коде, и я не мог использовать это решение, потому что разрыв был внутри другого цикла. Таким образом, решение goto определенно имеет варианты использования, где он более ясен / проще.
paercebal использует старый аргумент, что лучше избегать goto, потому что он избегает goto ... Я предполагаю, что он не совсем понимает значение errno == EINTR. Версия while совсем не яснее. Это создает совершенно неправильное впечатление, что у нас здесь есть цикл ожидания, который необходимо повторять, пока он не завершится успешно. Goto привлекает внимание читателя и заставляет его или ее фактически понимать кода.
Очень распространенный.
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) и другими конструкциями, которые увеличивают вложенность, сохраняя при этом читаемый структурированный код.
Я думаю, что это типичный способ обработки ошибок C. Я не вижу, чтобы он был заменен красиво, лучше читается в любом другом случае. С уважением
Почему бы и нет ... do_stuff(thingy) { RAIILockerObject(thingy); ..... /* -->*/ } /* <---*/ :)
@ ПетърПетров Это шаблон в C++, не имеющий аналогов в C.
Или на еще более структурированном языке, чем C++, try { lock();..code.. } finally { unlock(); }. Но да, у C нет эквивалента.
Классическая потребность в GOTO в C заключается в следующем.
for ...
for ...
if (breakout_condition)
goto final;
final:
Нет простого способа выйти из вложенных циклов без goto.
Чтобы добавить к этому: способ сделать это без goto - это установить логический флаг и тестировать этот флаг на каждой итерации каждого внешнего цикла. Это создает менее читаемый код; он также немного менее эффективен, но актуален только в редких случаях (например, код ядра).
Чаще всего, когда возникает такая необходимость, я могу использовать возврат. Но для этого нужно писать небольшие функции.
Я определенно согласен с Дариусом - преобразовать его в функцию и вместо этого вернуть!
Johnathan - break выходит только из ближайшего цикла. Дариус - конечно. Но это классический пример Goto In C.
@metao: Бьярн Страуструп не согласен. В его книге «Язык программирования C++» это как раз пример «правильного использования» goto.
@Adam Rosenfield: вся проблема с goto, выделенный Дейкстрой, заключается в том, что структурное программирование предлагает более понятные конструкции. Усложнение чтения кода во избежание перехода к goto доказывает, что автор не понял этого эссе ...
Зачем вам финал в этом коде? Разбейте его на функцию.
@Steve Jessop: Люди не только неправильно поняли эссе, но и большинство сектантов, которым "не идти", не понимают исторического контекста, в котором оно было написано.
Этот случай очень редок и является единственным случаем, когда я действительно использую Goto, особенно если функция сложная и стек вызовов не должен быть перефреймирован! Другой - на stackoverflow.com/a/16137870/625904
@DariusBacon На самом деле return не всегда приносит удовольствие, потому что, если вы, например, выделили память или другие ресурсы, вы, вероятно, захотите освободить их перед возвратом, вот где goto великолепен.
@ Ignas2526 На самом деле возврата достаточно. если вы беспокоитесь об освобождении памяти, занимающей время выполнения, используйте встроенную функцию. если вы хотите сохранить значение, используйте указатели
Современные компиляторы @MuhammadAnnaqeeb игнорируют встроенные функции и имеют собственное мнение о встраивании функций, поэтому нет никаких гарантий. Допустим, вы выделяете A, делаете что-то, затем B, делаете что-то, затем C и так далее ... и в любой момент вы можете потерпеть неудачу, и вам нужно освободить только текущий и предыдущий, например если вы потерпите неудачу в B, вы освободите B и A, но не C и выше. В этом сценарии без goto вы получите тонну кода от встраивания или тонну вызовов функций.
Я на 95% уверен, что оператор return сгенерирует переход к концу функции, которую нужно вернуть (при условии, что у вас есть фрейм стека, который нужно свернуть), поэтому на самом деле это не отличается от использования goto, за исключением удобочитаемости исходного кода. Но, как уже указывалось, goto - это четырехбуквенное слово ...;)
Несмотря на то, что со временем я возненавидел этот шаблон, он встроен в программирование 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;
}
Я сам не использую goto, однако однажды я работал с человеком, который использовал бы их в определенных случаях. Если я правильно помню, его объяснение было связано с проблемами производительности - у него также были определенные правила для как. Всегда в одной и той же функции, и метка всегда была НИЖЕ оператора goto.
Дополнительные данные: обратите внимание, что вы не можете использовать goto для выхода за пределы функции, и что в C++ запрещено обходить построение объекта с помощью goto.
Кнут написал статью «Структурированное программирование с операторами GOTO», вы можете получить ее, например. из здесь. Вы найдете там много примеров.
Эта бумага выглядит глубокой. Я все же прочту это. Спасибо
Эта статья так устарела, это даже не смешно. Предположения Кнута здесь просто не актуальны.
Какие предположения? Его примеры сегодня столь же реальны для процедурного языка, как C (он приводит их в некотором псевдокоде), как и в то время.
Я разочарован, что вы не написали :: Вы можете НАЙТИ 'здесь', чтобы получить это, каламбур для меня было бы невыносимым не сделать!
Я видел, как 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
}
Никогда и никогда не сравнивайте логическое значение с истиной или ложью. Это уже логическое значение; просто используйте "doBsuccess = doAsuccess && doB ();" и вместо этого "if (! doCsuccess) {}". Это проще и защищает вас от умных программистов, которые пишут такие вещи, как "#define true 0", потому что, ну, потому что они могут. Он также защищает вас от программистов, которые смешивают int и bool, а затем предполагают, что любое ненулевое значение истинно.
Спасибо и точка снята == true удалена. Во всяком случае, это ближе к моему настоящему стилю. :)
#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);
}
Я не понимаю, почему готофобы просто не предписывают "#define goto report_to_your_supervisor_for_re_education_through_labour" в верхней части включаемого файла проекта. Если это всегда неправильно, сделайте это невозможным. В остальном иногда бывает правильно ...