Выгодно ли когда-либо использовать "goto" на языке, поддерживающем циклы и функции? Если так, то почему?
у меня давно сложилось впечатление, что goto
никогда не следует использовать, если это возможно. Просматривая libavcodec (который написан на C) на днях, я заметил его многократное использование. Это когда-нибудь выгодно использовать goto
на языке, который поддерживает циклы и функции? Если так, то почему?
24 ответов
есть несколько причин для использования заявления "goto", о котором я знаю (некоторые уже говорили об этом):
чистый выход из функции
часто в функции вы можете распределять ресурсы и выходить из нее в нескольких местах. Программисты могут упростить свой код, поместив код очистки ресурсов в конец функции, и все "точки выхода" функции получат метку очистки. Таким образом, вам не нужно писать код очистки в каждой "точке выхода" функции.
выход из вложенных циклов
Если вы находитесь во вложенном цикле и вам нужно вырваться из все loops, goto может сделать это намного чище и проще, чем операторы break и if-checks.
улучшения производительности низкого уровня
это допустимо только в perf-критическом коде, но операторы goto выполняются очень быстро и могут дать вам импульс при перемещении через функцию. Однако это обоюдоострый меч, поскольку компилятор обычно не может оптимизировать код, содержащий gotos.
обратите внимание, что во всех этих примерах gotos ограничены областью действия одной функции.
все, кто против-goto
ссылается, прямо или косвенно, Дейкстра Edsger Гото Считается Вредным статьи для обоснования своей позиции. Жаль, что статья Дейкстры практически ничего С goto
операторы используются в наши дни, и поэтому то, что говорится в статье, практически не применимо к современной сцене программирования. The goto
-меньше мем граничит теперь с религией, вплоть до ее писаний, продиктованных сверху, его первосвященники и избегающие (или хуже) воспринимаемых еретиков.
давайте поместим статью Дийкстры в контекст, чтобы пролить немного света на эту тему.
когда Дийкстра писал свою статью, популярные языки того времени были неструктурированными процедурными, такими как BASIC, FORTRAN (более ранние диалекты) и различные языки ассемблера. Это было довольно распространено для людей, использующих языки более высокого уровня, чтобы прыгать по всей их кодовой базе в перекосило, перекосило потоков выполнения, которые породили термин "спагетти-код". Вы можете увидеть это, прыгнув на классическая игра Trek написано Майком Мэйфилдом и пытается выяснить, как все работает. Потратьте несколько минут, чтобы просмотреть это.
этой это "необузданное использование заявления go to", против которого Дийкстра выступал в своей статье в 1968 году. этой это среда, в которой он жил, которая заставила его написать эту статью. Способность он критиковал и требовал, чтобы его остановили, чтобы он прыгнул куда угодно в вашем коде в любой момент. Сравнивая это с анемическими способностями goto
В C или других таких более современных языках просто смешно.
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
вместоgoto
. Они часто хуже, чем разумное использованиеgoto
.
имейте в виду, пока вы голосуете за меня с одним -1 после другого, что у меня есть используется goto
в моем собственном (не ассемблерном) коде ровно 3 раза за последние 15-20 лет.
я жду потока возмущенных криков и -1 голосов, затаив дыхание.
повинуясь слепо лучшей практики не рекомендуется. Идея избежать goto
операторы в качестве основной формы управления потоком-избегать создания нечитаемого кода спагетти. Если использовать их экономно в нужных местах, они иногда могут быть самым простым и ясным способом выражения идеи. Уолтер Брайт, создатель компилятора Zortech C++ и языка программирования D, использует их часто, но разумно. Даже с goto
утверждения, его код все еще совершенно читаемый.
итог: избежать goto
во избежание goto
бессмысленно. Чего вы действительно хотите избежать, так это создания нечитаемого кода. Если goto
-загруженный код читается, тогда в нем нет ничего плохого.
С goto
делает рассуждения о потоке программы трудно1 (он же. "спагетти-код"), goto
обычно используется только для компенсации отсутствующих функций: использование goto
может быть действительно приемлемым, но только если язык не предлагает более структурированный вариант для достижения той же цели. Возьмите пример сомнения:
правило с goto, которое мы используем, заключается в том, что goto подходит для перехода к одной точке очистки выхода в функция.
это правда-но только если язык не позволяет структурированную обработку исключений с кодом очистки (например, 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
на C# переключатель сообщении doest не позволяют провалиться. Так что перейти используется для передачи управления на конкретную метку коммутатора или по умолчанию метки.
например:
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
это позволяет реализовать хвостовые вызовы бедняков. :- P
sub factorial {
my ($n, $acc) = (@_, 1);
return $acc if $n < 1;
@_ = ($n - 1, $acc * $n);
goto &factorial;
}
#endif
итак, не имеет ничего общего с Си 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 подходит для перехода к одной точке очистки выхода в функции. В действительно сложных функциях мы ослабляем это правило, чтобы позволить другим прыгать вперед. В обоих случаях мы избегаем глубоко вложенных операторов 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, no. 4. С. 261 - 301).
неудивительно, что некоторые аспекты этой 39-летней статьи датированы: порядки величины увеличение вычислительной мощности делает некоторые улучшения производительности кнута незаметными для проблем среднего размера, и с тех пор были изобретены новые конструкции языка программирования. (Например, блоки 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
в обеспечьте рудиментарную форму оптимизации tail-call.
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
$tally *= $n--;
@_ = ($n,$tally);
goto &factorial;
}
(In Perl 5 версия 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
без управления значением A.
этот пример очень очевиден, но по мере того, как программы усложняются, трудность видеть такие вещи увеличивается.
связанный материал можно найти в знаменитой статье от г-на Дейкстры "дело против заявления 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. Итак, кто прав: Dijkstra или Linus (и все кодеры ядра 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 не позволяет целевым объектам, которые компилятор не может легко вычислить, например gotos для вычисляемых адресов (**), компилятору не сложнее найти недостижимый код внутри функции с gotos, чем использовать код, одобренный Dijkstra.
(**) сноска: переход к вычисляемым номерам строк возможен в некоторых версиях Basic, например GOTO 10 * x, где x-переменная. Довольно запутанно, в Fortran "computed goto" относится к конструкции, эквивалентной оператору switch в C. Standard C не позволяет вычислять goto на языке, а только gotos для статически/синтаксически объявленных меток. Однако GNU C имеет расширение для получения адреса метки (унарного, префиксного && оператора), а также позволяет перейти к переменной типа void*. Вижу https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html более подробно об этом непонятных подтеме. Остальная часть этого сообщения не касается с этой непонятной функцией GNU C.
стандартные 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 "из цикла - используя оператор" last", который похож на break.
Это позволяет лучше контролировать вложенные циклы.
традиционный Гото метка тоже поддерживаются, но я не уверен, что есть слишком много случаев, когда это единственный способ достичь того, чего вы хотите, подпрограмм и циклов должно быть достаточно для большинства случаев.
проблема с "goto" и самым важным аргументом движения "goto-less programming" заключается в том, что если вы используете его слишком часто, ваш код, хотя он может вести себя правильно, становится нечитаемым, недостижимым, невидимым и т. д. В 99,99% случаев "Гото" приводит к коду спагетти. Лично я не могу придумать никакой веской причины, почему я бы использовал "goto".
Эдсгер Дейкстра, ученый-компьютерщик, который внес большой вклад в эту область, также был известен критикой использования GoTo. Есть короткая статья о его довод о Википедия.
Я использую goto в следующем случае: когда нужно вернуться из работают в разных местах, и перед возвращением некоторых инициализации нужно сделать:
версия non-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-бесплатный код:
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
выглядит более знакомо) - extra
loop
переменная необходима, используется в четырех места - требуется больше времени, чтобы прочитать и понять работу с
loop
- на
loop
не содержит никаких данных, он просто контролирует поток выполнения, что менее понятно, чем простая метка
дело в том, что Гото можно легко злоупотреблять, но сам Гото не виноват. Обратите внимание, что метка имеет область функции в C++, поэтому она не загрязняет глобальную область, как в чистой сборке, в которой перекрывающиеся петли имеет свое место и очень распространены - как в следующем коде для 8051, где 7segment дисплей подключен к P1. Программа обводит сегмент lightning вокруг:
; 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