Всегда ли выгодно использовать 'goto' на языке, который поддерживает циклы и функции? Если да, то почему?
у меня уже давно сложилось впечатление, что goto
никогда не следует использовать, если это возможно. Просматривая libavcodec (который написан на C) на днях, я заметил его многократное использование. Это когда-нибудь выгодно использовать goto
на языке, который поддерживает циклы и функции? Если да, то почему?
24 ответа:
есть несколько причин для использования утверждения" goto", о котором я знаю (некоторые уже говорили об этом):
чисто выход из функции
часто в функции, вы можете выделить ресурсы и нужно выйти в нескольких местах. Программисты могут упростить свой код, поместив код очистки ресурсов в конец функции, и все "точки выхода" функции получат метку очистки. Таким образом, вам не нужно писать код очистки в каждой "точке выхода" функции.
выход из вложенных циклов
Если вы находитесь во вложенном цикле и должны вырваться из все loops, goto может сделать это намного чище и проще, чем инструкции break и if-checks.
улучшения производительности низкого уровня
это допустимо только в perf-критическом коде, но операторы goto выполняются очень быстро и могут дать вам импульс при перемещении через функцию. Однако это обоюдоострый меч, поскольку компилятор обычно не может оптимизировать код, содержащий gotos.
обратите внимание, что во всех этих примерах gotos ограничены областью действия одной функции.
все, кто против-
goto
ссылается, прямо или косвенно, Дейкстра Edsger Гото Считается Вредным статьи для обоснования своей позиции. Жаль, что статья Дейкстры имеет практически ничего Сgoto
операторы используются в наши дни, и поэтому то, что говорится в статье, практически не применимо к современной сцене программирования. Элементgoto
- меньше мем граничит теперь с религией, вплоть до ее писаний, продиктованных сверху, его первосвященники и избегание (или хуже) воспринимаемых еретиков.давайте поместим статью Дейкстры в контекст, чтобы пролить немного света на эту тему.
по всей их кодовой базе в скрученном, искаженном виде потоков выполнения, которые породили термин "спагетти-код". Вы можете увидеть это, прыгнув на классическая игра Trek написано Майком Мэйфилдом и пытается выяснить, как все работает. Потратьте несколько минут, чтобы посмотреть на это.этой это "необузданное использование утверждения go to", против которого Дейкстра выступал в своей статье в 1968 году. этой это среда, в которой он жил, что привело его к написанию этой статьи. Способность прыгать куда угодно в вашем коде в любой момент, который вам нравится, - это то, что он критиковал и требовал, чтобы его остановили. Сравнивая это с анемичными силами
goto
в C или других таких более современных языках просто смешно.я уже слышу громкие песнопения культистов, когда они сталкиваются с еретиком. "Но, - будут скандировать они, - вы можете сделать код очень трудным для чтения с
goto
В С. " О да? Вы можете сделать код очень трудно читать безgoto
Как хорошо. Как вот этот:#define _ -F<00||--F-OO--; int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO() { _-_-_-_ _-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_ _-_-_-_ }
не
goto
в поле зрения, так что это должно быть легко читать, не так ли? Или как насчет этого:a[900]; b;c;d=1 ;e=1;f; g;h;O; main(k, l)char* *l;{g= atoi(* ++l); for(k= 0;k*k< g;b=k ++>>1) ;for(h= 0;h*h<= g;++h); --h;c=( (h+=g>h *(h+1)) -1)>>1; while(d <=g){ ++O;for (f=0;f< O&&d<=g ;++f)a[ b<<5|c] =d++,b+= e;for( f=0;f<O &&d<=g; ++f)a[b <<5|c]= d++,c+= e;e= -e ;}for(c =0;c<h; ++c){ for(b=0 ;b<k;++ b){if(b <k/2)a[ b<<5|c] ^=a[(k -(b+1)) <<5|c]^= a[b<<5 |c]^=a[ (k-(b+1 ))<<5|c] ;printf( a[b<<5|c ]?"%-4d" :" " ,a[b<<5 |c]);} putchar( '\n');}} /*Mike Laman*/
нет
goto
там. Поэтому он должен быть читаемым.что я имею в виду с этими примерами? Это не языковые функции, которые делают нечитаемый, недостижимый код. Это не синтаксис, который делает это. Это плохие программисты, которые вызывают это. И плохие программисты, как вы можете видеть в этом пункте выше, могут сделать любой языковая функция нечитабельна и непригодна для использования. Как и
for
петли там. (Вы можете видеть их, не так ли?)теперь, чтобы быть справедливым, некоторые языковые конструкции легче злоупотреблять, чем другие. Однако, если вы программист на C, я бы гораздо более внимательно изучил около 50% использования
#define
задолго до того, как я отправлюсь в крестовый поход противgoto
!Итак, для тех, кто потрудился прочитать это далеко, есть несколько ключевых моментов, чтобы отметить.
- Дейкстры бумага на
goto
операторы были написаны для среды программирования, гдеgoto
был много более потенциально опасного, чем в большинстве современных языков это не ассемблер.- автоматически выбрасывая все виды использования
goto
из-за этого примерно так же разумно, как сказать: "я пытался чтобы повеселиться один раз, но не понравилось, так что теперь я против этого".- есть законное использование современных (анемичных)
goto
операторы в коде, которые не могут быть адекватно заменены другими конструкциями.- есть, конечно, незаконное использование одних и тех же утверждений.
- есть также незаконное использование современных управляющих утверждений, таких как"
godo
" мерзость, где всегда-ложьdo
выход из цикла, используяbreak
вместо agoto
. Они часто хуже, чем разумное использованиеgoto
.имейте в виду, пока вы голосуете за меня с одним -1 после другого, что у меня есть используется
goto
в моей (не ассемблер) код точно 3 раза за последние 15-20 лет.я жду потока возмущенных криков и -1 голосов, затаив дыхание.
слепое следование лучшим практикам не является лучшей практикой. Идея избежать
goto
операторы в качестве основной формы управления потоком-это избежать создания нечитаемого кода спагетти. Если их использовать экономно в нужных местах, они иногда могут быть самым простым и ясным способом выражения идеи. Уолтер Брайт, создатель компилятора Zortech C++ и языка программирования D, использует их часто, но разумно. Даже сgoto
заявления, его код по-прежнему отлично читаемый.итог: избежать
goto
во избежаниеgoto
бессмысленно. То, что вы действительно хотите избежать, - это создание нечитаемого кода. Если вашgoto
-загруженный код читается, тогда в этом нет ничего плохого.
С
goto
делает рассуждения о потоке программы трудно1 (ака. "спагетти-код"),goto
обычно используется только для компенсации недостающих функций: использованиеgoto
на самом деле может быть приемлемым, но только если язык не предлагает более структурированный вариант для достижения той же цели. Возьмите пример сомнения:правило с goto, которое мы используем, заключается в том, что goto подходит для перехода вперед к одной точке очистки выхода в a функция.
это верно – но только если язык не позволяет структурированную обработку исключений с кодом очистки (например, RAII или
finally
), что делает ту же работу лучше (поскольку она специально построена для этого), или когда есть веская причина не использовать структурированную обработку исключений (но у вас никогда не будет этого случая, кроме как на очень низком уровне).в большинстве других языков, только приемлемое использование
goto
- выход из вложенных циклов. И даже там почти всегда лучше поднять внешний цикл в собственный метод и использоватьreturn
вместо.кроме этого,
goto
это признак того, что в данный фрагмент кода вошло недостаточно мысли.
1 современные языки, которые поддерживают
goto
реализовать некоторые ограничения (например,goto
не может прыгать в или из функций), но проблема принципиально остается той же.кстати, то же самое, конечно, тоже правда для других языковых функций, в первую очередь исключения. И обычно существуют строгие правила, чтобы использовать эти функции только там, где указано, например, правило не использовать исключения для управления не исключительным потоком программ.
Ну, есть одна вещь, которая всегда хуже, чем
goto's
; странное использование других операторов programflow, чтобы избежать goto:примеры:
// 1 try{ ... throw NoErrorException; ... } catch (const NoErrorException& noe){ // This is the worst } // 2 do { ...break; ...break; } while (false); // 3 for(int i = 0;...) { bool restartOuter = false; for (int j = 0;...) { if (...) restartOuter = true; if (restartOuter) { i = -1; } } etc etc
In C#переключатель сообщении делаешь не позволит падать-через. Так что перейти используется для передачи управления на определенную метку корпуса коммутатора или по умолчанию метки.
например:
switch(value) { case 0: Console.Writeln("In case 0"); goto case 1; case 1: Console.Writeln("In case 1"); goto case 2; case 2: Console.Writeln("In case 2"); goto default; default: Console.Writeln("In default"); break; }
Edit: есть одно исключение из правила "без провала". Провал разрешен, если оператор case не имеет кода.
#ifdef TONGUE_IN_CHEEK
Perl имеет
goto
это позволяет реализовать хвостовые вызовы бедняка. :- Psub factorial { my ($n, $acc) = (@_, 1); return $acc if $n < 1; @_ = ($n - 1, $acc * $n); goto &factorial; }
#endif
хорошо, так что это не имеет никакого отношения к C
goto
. Более серьезно, я согласен с другими комментариями об использованииgoto
для очистки, или для реализации устройство Даффа, или тому подобное. Все дело в использовании, а не в злоупотреблении.(тот же комментарий может применяться к
longjmp
, исключенияcall/cc
, и тому подобное-они имеют законное использование, но могут легко злоупотреблять. Например, выбрасывание исключения исключительно для того, чтобы избежать глубоко вложенной структуры управления, при совершенно не исключительных обстоятельствах.)
Я написал больше, чем несколько строк языка ассемблера за эти годы. В конечном счете, каждый язык высокого уровня компилируется до gotos. Хорошо, называйте их "ветки" или "прыжки" или что-то еще, но они goto. Может ли кто-нибудь написать goto-less ассемблер?
теперь, конечно, вы можете указать на Fortran, C или базовый программист, что запустить бунт с gotos-это рецепт спагетти болоньезе. Однако ответ заключается не в том, чтобы избегать их, а в том, чтобы использовать их осторожно.
нож может быть использован для приготовления пищи, освободить кого-то, или убить кого-то. Обходимся ли мы без ножей из-за страха перед последними? Точно так же Гото: используется небрежно, это мешает, используется осторожно, это помогает.
посмотри когда использовать Goto при программировании на C:
хотя использование goto почти всегда плохая практика программирования (конечно, вы можете найти лучший способ сделать XYZ), бывают случаи, когда это действительно не плохой выбор. Некоторые могут даже утверждать, что, когда это полезно, это лучший выбор.
большая часть того, что я должен сказать о goto, действительно относится только к C. Если вы используете C++, нет никакой разумной причины использовать goto на месте из исключений. Однако в C у вас нет возможности механизма обработки исключений, поэтому, если вы хотите отделить обработку ошибок от остальной логики вашей программы, и вы хотите избежать перезаписи кода очистки несколько раз по всему коду, то goto может быть хорошим выбором.
что я имею в виду? У вас может быть какой-то код, который выглядит так:
int big_function() { /* do some work */ if([error]) { /* clean up*/ return [error]; } /* do some more work */ if([error]) { /* clean up*/ return [error]; } /* do some more work */ if([error]) { /* clean up*/ return [error]; } /* do some more work */ if([error]) { /* clean up*/ return [error]; } /* clean up*/ return [success]; }
это нормально, пока вы не поймете, что вам нужно изменить код очистки. Тогда вам придется пройти и сделать 4 изменения. Теперь вы можете решить, что вы можете просто инкапсулировать всю очистку в одну функцию; это не плохая идея. Но это означает, что вам нужно быть осторожным с указателями-если вы планируете освободить указатель в своей функции очистки, нет никакого способа установить его на значение NULL, если вы не передадите указатель на указатель. Во многих случаях вы все равно не будете использовать этот указатель, так что это может не быть серьезной проблемой. С другой стороны, если добавить в новый указатель, дескриптор файла или другая вещь, которая нуждается в очистке, тогда вам нужно будет снова изменить свою функцию очистки; и тогда вам нужно будет изменить аргументы этой функции.
С помощью
goto
, будетint big_function() { int ret_val = [success]; /* do some work */ if([error]) { ret_val = [error]; goto end; } /* do some more work */ if([error]) { ret_val = [error]; goto end; } /* do some more work */ if([error]) { ret_val = [error]; goto end; } /* do some more work */ if([error]) { ret_val = [error]; goto end; } end: /* clean up*/ return ret_val; }
преимущество здесь заключается в том, что ваш код после завершения имеет доступ ко всему, что ему нужно будет выполнить очистку, и вам удалось значительно уменьшить количество точек изменения. Еще одно преимущество заключается в том, что вы ушли от множественного выхода очки для вашей функции только один; нет никаких шансов, что вы случайно вернетесь из функции без очистки.
кроме того, поскольку goto используется только для перехода к одной точке, это не так, как если бы вы создавали массу спагетти-кода, прыгающего туда и обратно в попытке имитировать вызовы функций. Скорее, goto действительно помогает писать более структурированный код.
одним словом,
goto
всегда следует использовать экономно, и в крайнем случае -- но для этого есть время и место. Вопрос должен быть не "вы должны использовать его", а" это лучший выбор", чтобы использовать его.
мне кажется забавным, что некоторые люди пойдут так далеко, чтобы дать список случаев, когда goto является приемлемым, говоря, что все другие виды использования неприемлемы. Вы действительно думаете, что знаете каждый случай, когда goto является лучшим выбором для выражения алгоритма?
чтобы проиллюстрировать, я приведу вам пример, который еще никто здесь не показывал:
сегодня я писал код для вставки элемента в хэш-таблицу. Хэш-таблица-это кэш предыдущих вычислений, который может быть перезаписывается по желанию (влияет на производительность, но не на корректность).
каждое ведро хэш-таблицы имеет 4 слота, и у меня есть куча критериев, чтобы решить, какой элемент перезаписать, когда ведро заполнено. Прямо сейчас это означает сделать до трех проходов через ведро, как это:
// Overwrite an element with same hash key if it exists for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if (slot_p[add_index].hash_key == hash_key) goto add; // Otherwise, find first empty element for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if ((slot_p[add_index].type == TT_ELEMENT_EMPTY) goto add; // Additional passes go here... add: // element is written to the hash table here
теперь, если бы я не использовал goto, как бы выглядел этот код?
что-то вроде этого:
// Overwrite an element with same hash key if it exists for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if (slot_p[add_index].hash_key == hash_key) break; if (add_index >= ELEMENTS_PER_BUCKET) { // Otherwise, find first empty element for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++) if ((slot_p[add_index].type == TT_ELEMENT_EMPTY) break; if (add_index >= ELEMENTS_PER_BUCKET) // Additional passes go here (nested further)... } // element is written to the hash table here
это будет выглядеть все хуже и хуже, если будет добавлено больше проходов, в то время как версия с goto сохраняет тот же уровень отступа во все времена и избегает использования ложных операторов if, результат которых подразумевается выполнением предыдущего цикла.
Так есть еще один случай, когда goto делает код чище и легче писать и понимать... Я уверен, что есть еще много, поэтому не притворяйтесь, что знаете все случаи, когда goto полезен, презирая любые хорошие, о которых вы не могли думать.
правило с goto, которое мы используем, заключается в том, что goto подходит для перехода к одной точке очистки выхода в функции. В действительно сложных функциях мы ослабляем это правило, чтобы позволить другим прыгать вперед. В обоих случаях мы избегаем глубоко вложенных операторов if, которые часто встречаются с проверкой кода ошибки, что помогает читаемости и обслуживанию.
одна из причин goto плохо, кроме того, стиль кодирования является то, что вы можете использовать его для создания перекрытие, а невложенной петли:
loop1: a loop2: b if(cond1) goto loop1 c if(cond2) goto loop2
Это создало бы причудливую, но, возможно, законную структуру управления потоком, где последовательность, подобная (a, b, c, b, a, b, a, b,...) возможно, что делает компилятор хакеров несчастными. По-видимому, существует ряд умных оптимизационных трюков, которые полагаются на этот тип структуры, не происходящий. (Я должен проверить мой копия книги о драконах...) Результатом этого может быть (с использованием некоторых компиляторов) то, что другие оптимизации не выполняются для кода, содержащего
goto
s.Это может быть полезно, если вы знаю это просто, "о, кстати", случается, чтобы убедить компилятор выдавать более быстрый код. Лично я предпочел бы попытаться объяснить компилятору о том, что вероятно, а что нет, прежде чем использовать трюк, такой как goto, но, возможно, я также могу попробовать
goto
перед взломом ассемблера.
наиболее вдумчивым и тщательным обсуждением утверждений goto, их законного использования и альтернативных конструкций, которые могут использоваться вместо "добродетельных утверждений goto", но могут быть использованы так же легко, как и утверждения goto, является статья Дональда Кнута"структурированное Программирование с помощью операторов goto", в декабрьских 1974 вычислительных обзорах (том 6, № 4. С. 261 - 301).
неудивительно, что некоторые аспекты этой 39-летней статьи датированы: порядки величины увеличение вычислительной мощности делает некоторые улучшения производительности Knuth незаметными для задач среднего размера, и с тех пор были изобретены новые конструкции языка программирования. (Например, блоки try-catch включают конструкцию Zahn, хотя они редко используются таким образом.) Но кнут охватывает все стороны аргумента и должен быть обязательно прочитан, прежде чем кто-либо снова перефразирует вопрос.
в модуле Perl иногда требуется создавать подпрограммы или замыкания на лету. Дело в том, что как только вы создали подпрограмму, как вы до нее доберетесь. Вы можете просто вызвать его, но тогда, если подпрограмма использует
caller()
это будет не так полезно, как могло бы быть. Вот гдеgoto &subroutine
вариации могут быть полезны.вот пример:
sub AUTOLOAD{ my($self) = @_; my $name = $AUTOLOAD; $name =~ s/.*:://; *{$name} = my($sub) = sub{ # the body of the closure } goto $sub; # nothing after the goto will ever be executed. }
вы также можете использовать данную форму
goto
to обеспечьте рудиментарную форму оптимизации хвостового вызова.sub factorial($){ my($n,$tally) = (@_,1); return $tally if $n <= 1; $tally *= $n--; @_ = ($n,$tally); goto &factorial; }
(In Perl 5 version 16 это было бы лучше написать как
goto __SUB__;
)есть модуль, который будет импортировать
tail
модификатор и тот, который будет импортироватьrecur
если вам не нравится использовать эту формуgoto
.use Sub::Call::Tail; sub AUTOLOAD { ... tail &$sub( @_ ); } use Sub::Call::Recur; sub factorial($){ my($n,$tally) = (@_,1); return $tally if $n <= 1; recur( $n-1, $tally * $n ); }
большинство других причин для использования
goto
лучше сделать с другие ключевые слова.как
redo
ing немного кода:LABEL: ; ... goto LABEL if $x;
{ ... redo if $x; }
или
last
немного кода из нескольких мест:goto LABEL if $x; ... goto LABEL if $y; ... LABEL: ;
{ last if $x; ... last if $y ... }
если да, то почему?
C не имеет многоуровневого / помеченного разрыва, и не все потоки управления можно легко смоделировать с помощью итераций C и примитивов решений. gotos пройти долгий путь к исправлению этих недостатков.
иногда более ясно использовать переменную флага какого-либо вида, чтобы произвести своего рода псевдо-многоуровневый разрыв, но он не всегда превосходит goto (по крайней мере, goto позволяет легко определить, куда идет управление, в отличие от флага переменная), а иногда вы просто не хотите платить цену производительности флагов/других искажений, чтобы избежать goto.
libavcodec-это чувствительный к производительности фрагмент кода. Прямое выражение потока управления, вероятно, является приоритетом, потому что он будет работать лучше.
Я нахожу использование do{} while (false) совершенно отвратительным. Возможно, это может убедить меня, что это необходимо в каком-то странном случае, но никогда, что это чистый разумный код.
Если вы должны сделать какой-то такой цикл, почему бы не сделать зависимость от переменной флага явной?
for (stepfailed=0 ; ! stepfailed ; /*empty*/)
GOTO можно использовать, конечно, но есть еще одна важная вещь, чем стиль кода, или если код является или не читается, что вы должны иметь в виду, когда вы его используете:код внутри может быть не так надежен, как вы думаете.
например, посмотрите на следующие два фрагмента кода:
If A <> 0 Then A = 0 EndIf Write("Value of A:" + A)
эквивалентный код с GOTO
If A == 0 Then GOTO FINAL EndIf A = 0 FINAL: Write("Value of A:" + A)
первое, что мы думаем, что результатом обоих битов кода будет то, что " значение A: 0" (мы предполагаем выполнение без параллелизма, конечно)
Это неверно: в первом примере A всегда будет 0, но во втором примере (с оператором GOTO) A может не быть 0. Зачем?
причина в том, что с другой точки программы я могу вставить
GOTO FINAL
без контроля величины А.этот пример очень очевиден, но поскольку программы становятся все более сложными, трудно увидеть такие вещи увеличивается.
связанный материал можно найти в известной статье от г-на Дейкстры "дело против заявления GO TO"
1) наиболее распространенное использование goto, о котором я знаю, - это эмуляция обработки исключений в языках, которые его не предлагают, а именно в C. (код, приведенный выше Nuclear, именно таков.) Посмотрите на исходный код Linux, и вы увидите, что таким образом используется bazillion gotos; в коде Linux было около 100 000 gotos согласно быстрому опросу, проведенному в 2013 году:http://blog.regehr.org/archives/894. Использование Goto даже упоминается в руководстве по стилю кодирования Linux: https://www.kernel.org/doc/Documentation/CodingStyle. так же, как объектно-ориентированное программирование эмулируется с использованием структур, заполненных указателями функций, goto имеет свое место в программировании на C. Так кто же прав: Дейкстра или Линус (и все кодеры ядра Linux)? Это теория против практики в основном.
однако есть обычная gotcha для отсутствия поддержки на уровне компилятора и проверки общих конструкций / шаблонов: проще использовать их неправильно и вводить ошибки без проверка времени компиляции. Windows и Visual C++, но в режиме C предлагают обработку исключений через SEH / VEH именно по этой причине: исключения полезны даже за пределами языков ООП, т. е. на процедурном языке. Но компилятор не всегда может сохранить ваш бекон, даже если он предлагает синтаксическую поддержку исключений в языке. Рассмотрим в качестве примера последнего случая знаменитую ошибку Apple SSL "goto fail", которая просто дублировала один goto с катастрофическими последствиями (https://www.imperialviolet.org/2014/02/22/applebug.html):
if (something()) goto fail; goto fail; // copypasta bug printf("Never reached\n"); fail: // control jumps here
вы можете иметь точно такую же ошибку, используя исключения, поддерживаемые компилятором, например, в C++:
struct Fail {}; try { if (something()) throw Fail(); throw Fail(); // copypasta bug printf("Never reached\n"); } catch (Fail&) { // control jumps here }
но оба варианта ошибки можно избежать, если компилятор анализирует и предупреждает Вас о недостижимом коде. Например, компиляция с Visual C++ на уровне предупреждения /W4 обнаруживает ошибку в обоих случаях. Java, например, запрещает недостижимый код (где он может его найти!) хорошая причина: скорее всего, это ошибка в коде среднего Джо. Пока конструкция goto не позволяет целевым объектам, которые компилятор не может легко вычислить, например goto для вычисляемых адресов (**), компилятору не сложнее найти недостижимый код внутри функции с goto, чем использовать код, одобренный Dijkstra.
(**) сноска: переход к вычисленным номерам строк возможен в некоторых версиях Basic, например GOTO 10*x, где x-переменная. Довольно смутно, в Фортране "вычисленный goto" относится к конструкции, которая эквивалентна оператору switch в C. стандарт C не позволяет вычислять goto на языке, но только goto для статически/синтаксически объявленных меток. Однако GNU C имеет расширение для получения адреса метки (унарный, префиксный && оператор), а также позволяет перейти к переменной типа void*. Смотрите https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html подробнее об этой малоизвестной подтеме. Остальная часть этого сообщения не касается с неясными характеристика языка Си.
стандартные C (т. е. не вычисленные) gotos обычно не являются причиной того, что недостижимый код не может быть найден во время компиляции. Обычная причина-это логический код, подобный следующему. Учитывая
int computation1() { return 1; } int computation2() { return computation1(); }
компилятору так же трудно найти недостижимый код в любой из следующих 3 конструкций:
void tough1() { if (computation1() != computation2()) printf("Unreachable\n"); } void tough2() { if (computation1() == computation2()) goto out; printf("Unreachable\n"); out:; } struct Out{}; void tough3() { try { if (computation1() == computation2()) throw Out(); printf("Unreachable\n"); } catch (Out&) { } }
(извините мой стиль кодирования, связанный с скобками, но я попытался сохранить примеры как можно более компактными.)
Visual C++ /W4 (даже с /Ox) не может найти недостижимый код в любом из них, и, как вы, вероятно, знаете, проблема поиска недостижимого кода неразрешима в целом. (Если вы не верите мне об этом:https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf)
как связанная проблема, c goto можно использовать для эмуляции исключений только внутри тела функции. Стандартная библиотека C предлагает пару функций setjmp () и longjmp () для эмуляции нелокальных функций выходы / исключения, но у них есть некоторые серьезные недостатки по сравнению с тем, что предлагают другие языки. Статья в Википедииhttp://en.wikipedia.org/wiki/Setjmp.h довольно хорошо объясняет этот последний вопрос. Эта пара функций также работает на Windows (http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx), но вряд ли кто-то использует их там, потому что SEH/VEH превосходит. Даже на Unix, я думаю, что setjmp и longjmp очень редко используются.
2) я думаю, что второй наиболее общее использование goto в C реализует многоуровневый перерыв или многоуровневое Продолжение, что также является довольно бесспорным случаем использования. Напомним, что Java не разрешает метку goto, но позволяет разбить метку или продолжить метку. Согласно http://www.oracle.com/technetwork/java/simple-142616.html, это на самом деле самый распространенный случай использования gotos в C (90% они говорят), но в моем субъективном опыте системный код чаще использует gotos для обработки ошибок. Возможно, в научном код или где ОС предлагает обработку исключений (Windows), то многоуровневые выходы являются доминирующим вариантом использования. Они действительно не дают никаких подробностей относительно контекста их опроса.
отредактировано для добавления: оказывается, эти два шаблона использования находятся в книге C Кернигана и Ричи, вокруг страницы 60 (в зависимости от издания). Другое дело, что оба варианта использования включают только вперед gotos. И получается, что MISRA C 2012 edition (в отличие от издания 2004 года) теперь разрешает gotos, как пока они только передние.
в Perl используйте метку для " goto "из цикла - используя" последний " оператор, который похож на break.
Это позволяет лучше контролировать вложенные циклы.
традиционный Гото метка тоже поддерживается, но я не уверен, что есть слишком много случаев, когда это единственный способ достичь того, что вы хотите - подпрограммы и циклы должны быть достаточными для большинства случаев.
проблема с "goto" и самый важный аргумент движения "goto-less programming" заключается в том, что если вы используете его слишком часто, ваш код, хотя он может вести себя правильно, становится нечитаемым, недостижимым, не видимым и т. д. В 99,99% случаев "Гото" приводит к спагетти-коду. Лично я не могу придумать ни одной веской причины, почему я бы использовал "goto".
Edsger Dijkstra, ученый-компьютерщик, который внес большой вклад в эту область, также был известен критикой использования GoTo. Есть небольшая статья о его аргументации на Википедия.
Я использую goto в следующем случае: когда нужно вернуться из работают в разных местах, и перед возвращением некоторых инициализации нужно сделать:
не Гото версия:
int doSomething (struct my_complicated_stuff *ctx) { db_conn *conn; RSA *key; char *temp_data; conn = db_connect(); if (ctx->smth->needs_alloc) { temp_data=malloc(ctx->some_size); if (!temp_data) { db_disconnect(conn); return -1; } } ... if (!ctx->smth->needs_to_be_processed) { free(temp_data); db_disconnect(conn); return -2; } pthread_mutex_lock(ctx->mutex); if (ctx->some_other_thing->error) { pthread_mutex_unlock(ctx->mutex); free(temp_data); db_disconnect(conn); return -3; } ... key=rsa_load_key(....); ... if (ctx->something_else->error) { rsa_free(key); pthread_mutex_unlock(ctx->mutex); free(temp_data); db_disconnect(conn); return -4; } if (ctx->something_else->additional_check) { rsa_free(key); pthread_mutex_unlock(ctx->mutex); free(temp_data); db_disconnect(conn); return -5; } pthread_mutex_unlock(ctx->mutex); free(temp_data); db_disconnect(conn); return 0; }
версия goto:
int doSomething_goto (struct my_complicated_stuff *ctx) { int ret=0; db_conn *conn; RSA *key; char *temp_data; conn = db_connect(); if (ctx->smth->needs_alloc) { temp_data=malloc(ctx->some_size); if (!temp_data) { ret=-1; goto exit_db; } } ... if (!ctx->smth->needs_to_be_processed) { ret=-2; goto exit_freetmp; } pthread_mutex_lock(ctx->mutex); if (ctx->some_other_thing->error) { ret=-3; goto exit; } ... key=rsa_load_key(....); ... if (ctx->something_else->error) { ret=-4; goto exit_freekey; } if (ctx->something_else->additional_check) { ret=-5; goto exit_freekey; } exit_freekey: rsa_free(key); exit: pthread_mutex_unlock(ctx->mutex); exit_freetmp: free(temp_data); exit_db: db_disconnect(conn); return ret; }
вторая версия упрощает, когда вам нужно что-то изменить в операторах освобождения (каждый используется один раз в коде), и уменьшает вероятность пропустить любой из них при добавлении новой ветви. Перемещение их в функции будет не поможет здесь и то, что освобождение можно сделать на разных "уровнях".
некоторые говорят, что нет никакой причины для goto в C++. Некоторые говорят, что в 99% случаев есть лучшие альтернативы. Чтобы быть конкретным, вот пример, где goto приводит к хорошему коду, что-то вроде расширенного цикла do-while:
int i; again: std::cout << "insert number: "; std::cin >> i; if(std::cin.fail()) { std::cin.clear(); std::cin.ignore(1000,'\n'); goto again; } std::cout << "your number is " << i;
сравните его с goto-free code:
int i; bool loop; do { loop = false; std::cout << "insert number: "; std::cin >> i; if(std::cin.fail()) { std::cin.clear(); std::cin.ignore(1000,'\n'); loop = true; } } while(loop); std::cout << "your number is " << i;
Я вижу эти различия:
- вложенные
{}
блок необходим (хотя иdo {...} while
выглядит более знакомо)- дополнительно
loop
переменная необходима, используется в четырех места- требуется больше времени, чтобы прочитать и понять работу с
loop
- the
loop
не содержит никаких данных, он просто управляет потоком выполнения, что менее понятно, чем простая меткадело в том, что Гото можно легко злоупотреблять, но сам Гото не виноват. Обратите внимание, что label имеет область действия в C++, поэтому он не загрязняет глобальную область, как в чистой сборке, в которой перекрывающиеся петли имеет свое место и очень распространен - как в следующем коде для 8051, где 7segment дисплей подключен к P1. Программа зацикливает сегмент молнии вокруг:
; P1 states loops ; 11111110 <- ; 11111101 | ; 11111011 | ; 11110111 | ; 11101111 | ; 11011111 | ; |_________| again: MOV P1,#11111110b ACALL delay loop: MOV A,P1 RL A MOV P1,A ACALL delay JNB P1.5, again SJMP loop