Полезно ли использовать «goto» на языке, который поддерживает циклы и функции? Если да, то почему?

У меня давно сложилось впечатление, что goto никогда не следует использовать, если это возможно. Во время просмотра libavcodec (который написан на C) на днях, я заметил несколько его применений. Полезно ли использовать goto на языке, который поддерживает циклы и функции? Если да, то почему?

Есть несколько причин использовать заявление «goto», о котором я знаю (некоторые уже говорили об этом):

Чисто выход из функции

Часто в функции вы можете выделять ресурсы и выходить из нее в нескольких местах. Программисты могут упростить свой код, поместив код очистки ресурсов в конце функции, и все «точки выхода» этой функции перейдут на метку очистки. Таким образом, вам не нужно писать код очистки в каждой «точке выхода» функции.

Выход из вложенных циклов

Если вы находитесь в вложенном цикле и должны вырваться из всех циклов, goto может сделать это намного чище и проще, чем фразы break и if-check.

Низкоуровневые улучшения производительности

Это справедливо только в персид-критическом коде, но операторы goto выполняются очень быстро и могут дать вам толчок при перемещении по функции. Это обоюдоострый меч, однако, поскольку компилятор обычно не может оптимизировать код, содержащий gotos.

Обратите внимание, что во всех этих примерах gotos ограничены областью действия одной функции.

Все, кто против, goto прямо или косвенно на « GoTo» Эдсгера Дейкстры, считают, что это вредная статья, чтобы обосновать свою позицию. Слишком плохо, что статья Дейкстры практически не имеет никакого отношения к тому, как в наши дни используются утверждения goto , и, следовательно, то, что говорится в статье, практически не применимо к современной сцене программирования. Безледный мем граничит с религией, вплоть до своих писаний, продиктованных высоко, его первосвященниками и избегающими (или худшими) воспринимаемыми еретиками.

Поставим статью Дейкстры в контекст, чтобы пролить немного света на эту тему.

Когда Дейкстра писал свою газету, популярные языки того времени были неструктурированными процедурами, такими как BASIC, FORTRAN (более ранние диалекты) и различные языки ассемблера. Для людей, использующих языки более высокого уровня, довольно часто перескакивали по всей своей кодовой базе в искаженных, искаженных streamах исполнения, что давало начало термину «код спагетти». Вы можете это увидеть, перепрыгнув на classическую игру Trek, написанную Майком Мэйфилдом, и попытаться выяснить, как все работает. Потратьте несколько минут, чтобы разобраться.

ЭТО – «необузданное использование заявления», которое Дейкстра критиковал в своей статье в 1968 году. ЭТО это среда, в которой он жил, что заставило его написать эту статью. Возможность прыгать куда угодно, как в вашем коде, в любой момент, которую вы любили, это то, что он критиковал и требовал прекратить. Сравнивая это с анемичными полномочиями goto на C или других таких более современных языках, просто просто.

Я уже слышу поднятые песнопения культистов, когда они сталкиваются с еретиком. «Но, – повторяют они, – вы можете сделать код очень трудным для чтения с 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() { _-_-_-_ _-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_ _-_-_-_ } 

Не то, что нужно видеть, так что его нужно легко читать, не так ли? Или как об этом:

 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 

Там тоже нет. Поэтому он должен быть читаемым.

Какова моя точка зрения с этими примерами? Это не языковые функции, которые делают нечитаемый, незаменимый код. Это не синтаксис, который делает это. Это плохие программисты, которые вызывают это. И плохие программисты, как вы можете видеть в этом выше, могут сделать любую языковую функцию нечитаемой и непригодной для использования. Как и for петель. (Вы можете их видеть, правда?)

Теперь, чтобы быть справедливым, некоторые языковые конструкции легче злоупотреблять, чем другие. Однако, если вы программист C, я бы посмотрел гораздо ближе примерно на 50% от использования #define задолго до того, как я отправился в крестовый поход против goto !

Итак, для тех, кто потрудился прочесть это, есть несколько ключевых моментов.

  1. Документ Дейкстры о goto был написан для среды программирования, где goto был намного более потенциально опасным, чем в большинстве современных языков, которые не являются ассемблером.
  2. Автоматически выбрасывать все виды использования goto из-за этого примерно так же рационально, как сказать: «Я пытался повеселиться один раз, но мне не понравилось, поэтому теперь я против».
  3. Существует законное использование современных (анемичных) goto в коде, которые не могут быть надлежащим образом заменены другими конструкциями.
  4. Конечно, есть незаконное использование тех же утверждений.
  5. Существуют также незаконные применения современных контрольных высказываний, таких как мерзость « godo », когда из-за break вместо goto происходит break всегда ложной goto . Это часто хуже, чем разумное использование goto .

Имейте в виду, в то время как вы голосуете меня с одним -1 за другим, что за последние 15-20 лет я использовал goto в своем (неассемблерном) кодексе ровно 3 раза.

Я ожидаю stream возмущенных криков и -1 голосов затаив дыхание.

Слепое соблюдение лучших практик – не лучшая практика. Идея избегать goto качестве основной формы управления streamом заключается в том, чтобы избежать создания нечитаемого кода спагетти. Если они используются редко в правильных местах, они иногда могут быть самым простым и ясным способом выражения идеи. Уолтер Брайт, создатель компилятора Zortech C ++ и языка программирования D, часто использует их, но разумно. Даже с утверждениями goto его код по-прежнему отлично читается.

Итог: избегать goto ради избежания goto бессмысленно. То, что вы действительно хотите избежать, это создание нечитаемого кода. Если ваш код goto paden доступен для чтения, то в этом нет ничего плохого.

Поскольку goto делает рассуждения о программном streamе жестким 1 (иначе говоря, «код спагетти»), goto обычно используется только для компенсации недостающих функций: использование goto может быть действительно приемлемым, но только если язык не предлагает более структурированный вариант для достижения той же цели. Возьмем пример Doubt:

Правило с goto, которое мы используем, – это то, что goto подходит для перепрыжки в одну точку очистки выхода в функции.

Это верно, но только в том случае, если язык не позволяет обрабатывать структурированные исключения с кодом очистки (например, RAII или, finally ), что делает ту же работу лучше (поскольку она специально создана для ее выполнения) или когда есть веская причина не использовать структурированную обработку исключений (но этого никогда не будет, кроме как на очень низком уровне).

В большинстве других языков единственным приемлемым использованием goto является выход из вложенных циклов. И даже там почти всегда лучше поднять внешний цикл в собственный метод и вместо этого использовать return .

Кроме этого, goto – это признак того, что недостаточно внимания уделяется конкретному fragmentу кода.


1 Современные языки, которые поддерживают goto реализуют некоторые ограничения (например, goto не может входить в функции или из них), но проблема принципиально не меняется.

Кстати, то же самое, конечно, верно и для других языковых функций, в первую очередь исключений. И, как правило, существуют строгие правила, позволяющие использовать эти функции только там, где это указано, например правило не использовать исключения для управления неисключительным streamом программы.

Ну, есть одна вещь, которая всегда хуже, чем у goto's ; странное использование других операторов программ, чтобы избежать перехода:

Примеры:

  // 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 # switch doest не допускается провал . Таким образом, goto используется для передачи управления на конкретную метку коммутатора или метку по умолчанию .

Например:

 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; } 

Изменить: Есть одно исключение из правила «без провала». Падение разрешено, если в case-case нет кода.

#ifdef TONGUE_IN_CHEEK

Perl имеет goto который позволяет вам реализовывать хвосты бедных людей. :-П

 sub factorial { my ($n, $acc) = (@_, 1); return $acc if $n < 1; @_ = ($n - 1, $acc * $n); goto &factorial; } 

#endif

Ладно, так что это не имеет никакого отношения к переходу C. Более серьезно, я согласен с другими комментариями относительно использования goto для очистки, или для реализации устройства Даффа , или тому подобного. Это все об использовании, а не оскорблении.

(Тот же комментарий может применяться к longjmp , exceptions, call/cc и т. Д. - они имеют законное использование, но их легко можно злоупотреблять. Например, исключение исключительного исключения исключительно из-за глубоко вложенной структуры управления, полностью непредвиденные обстоятельства.)

На протяжении многих лет я написал несколько строк ассемблера. В конечном счете, каждый язык высокого уровня компилируется до gotos. Хорошо, назовите их «ветками» или «прыжками» или что-то еще, но они есть. Может ли кто-нибудь писать goto-less ассемблер?

Теперь, конечно, вы можете указать программисту Fortran, C или BASIC, что запуск беспорядков с gotos – это рецепт для богенизы спагетти. Однако ответ заключается не в том, чтобы избегать их, а в том, чтобы использовать их осторожно.

Нож можно использовать для приготовления пищи, свободного человека или кого-то убить. Неужели мы обходимся без ножей из-за страха перед последним? Точно так же goto: используется небрежно это мешает, используется тщательно, это помогает.

Взгляните на « Когда использовать 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 является приемлемым, заявив, что все другие применения неприемлемы. Вы действительно думаете, что знаете каждый случай, когда goto – лучший выбор для выражения алгоритма?

Чтобы проиллюстрировать это, я приведу вам пример, который никто здесь не показал:

Сегодня я писал код для вставки элемента в хеш-таблицу. Хэш-таблица – это кеш предыдущих вычислений, которые могут быть перезаписаны по желанию (влияющие на производительность, но не на правильность).

Каждое ведро hash-таблицы имеет 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 подходит для перепрыжки в одну точку очистки выхода в функции. В действительно сложных функциях мы расслабляем это правило, чтобы позволить другому прыгать вперед. В обоих случаях мы избегаем глубоко вложенных утверждений, которые часто возникают при проверке кода ошибки, что помогает читать и поддерживать.

Одна из причин goto – плохо, кроме того, стиль кодирования заключается в том, что вы можете использовать его для создания перекрывающихся , но не вложенных циклов:

 loop1: a loop2: b if(cond1) goto loop1 c if(cond2) goto loop2 

Это создало бы причудливую, но, возможно, законную структуру управления streamом, в которой возможны последовательности, подобные (a, b, c, b, a, b, a, b, …), что делает хакеров-компиляторов недовольными. По-видимому, существует множество умных трюков оптимизации, которые полагаются на этот тип структуры, которая не происходит. (Я должен проверить свою копию книги драконов …). Результатом этого может быть (с использованием некоторых компиляторов) быть то, что другие оптимизации не выполняются для кода, содержащего goto s.

Было бы полезно, если бы вы знали это просто, «о, кстати», убеждает компилятор испускать более быстрый код. Лично я бы предпочел попытаться объяснить компилятору о том, что вероятно, и что еще не используется с трюком, вроде goto, но, возможно, я также могу попробовать goto до взлома ассемблера.

The most thoughtful and thorough discussion of goto statements, their legitimate uses, and alternative constructs that can be used in place of “virtuous goto statements” but can be abused as easily as goto statements, is Donald Knuth’s article ” Structured Programming with goto Statements “, in the December 1974 Computing Surveys (volume 6, no. 4. pp. 261 – 301).

Not surprisingly, some aspects of this 39-year old paper are dated: Orders-of-magnitude increases in processing power make some of Knuth’s performance improvements unnoticeable for moderately sized problems, and new programming-language constructs have been invented since then. (For example, try-catch blocks subsume Zahn’s Construct, although they are rarely used in that way.) But Knuth covers all sides of the argument, and should be required reading before anyone rehashes the issue yet again.

In a Perl module, you occasionally want to create subroutines or closures on the fly. The thing is, that once you have created the subroutine, how do you get to it. You could just call it, but then if the subroutine uses caller() it won’t be as helpful as it could be. That is where the goto &subroutine variation can be helpful.

Вот краткий пример:

 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. } 

You can also use this form of goto to provide a rudimentary form of tail-call optimization.

 sub factorial($){ my($n,$tally) = (@_,1); return $tally if $n <= 1; $tally *= $n--; @_ = ($n,$tally); goto &factorial; } 

( In Perl 5 version 16 that would be better written as goto __SUB__; )

There is a module that will import a tail modifier and one that will import recur if you don't like using this form of 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 ); } 

Most of the other reasons to use goto are better done with other keywords.

Like redo ing a bit of code:

 LABEL: ; ... goto LABEL if $x; 
 { ... redo if $x; } 

Or going to the last of a bit of code from multiple places:

 goto LABEL if $x; ... goto LABEL if $y; ... LABEL: ; 
 { last if $x; ... last if $y ... } 

Если да, то почему?

C has no multi-level/labelled break, and not all control flows can be easily modelled with C’s iteration and decision primitives. gotos go a long way towards redressing these flaws.

Sometimes it’s clearer to use a flag variable of some kind to effect a kind of pseudo-multi-level break, but it’s not always superior to the goto (at least a goto allows one to easily determine where control goes to, unlike a flag variable), and sometimes you simply don’t want to pay the performance price of flags/other contortions to avoid the goto.

libavcodec is a performance-sensitive piece of code. Direct expression of the control flow is probably a priority, because it’ll tend to run better.

Just as well no one ever implemented the “COME FROM” statement….

I find the do{} while(false) usage utterly revolting. It is conceivable might convince me it is necessary in some odd case, but never that it is clean sensible code.

If you must do some such loop, why not make the dependence on the flag variable explicit?

 for (stepfailed=0 ; ! stepfailed ; /*empty*/) 

The GOTO can be used, of course, but there is one more important thing than the code style, or if the code is or not readable that you must have in mind when you use it: the code inside may not be as robust as you think .

For instance, look at the following two code snippets:

 If A <> 0 Then A = 0 EndIf Write("Value of A:" + A) 

An equivalent code with GOTO

 If A == 0 Then GOTO FINAL EndIf A = 0 FINAL: Write("Value of A:" + A) 

The first thing we think is that the result of both bits of code will be that “Value of A: 0” (we suppose an execution without parallelism, of course)

That’s not correct: in the first sample, A will always be 0, but in the second sample (with the GOTO statement) A might not be 0. Why?

The reason is because from another point of the program I can insert a GOTO FINAL without controlling the value of A.

This example is very obvious, but as programs get more complicated, the difficulty of seeing those kind of things increases.

Related material can be found into the famous article from Mr. Dijkstra “A case against the GO TO statement”

1) The most common use of goto that I know of is emulating exception handling in languages that don’t offer it, namely in C. (The code given by Nuclear above is just that.) Look at the Linux source code and you’ll see a bazillion gotos used that way; there were about 100,000 gotos in Linux code according to a quick survey conducted in 2013: http://blog.regehr.org/archives/894 . Goto usage is even mentioned in the Linux coding style guide: https://www.kernel.org/doc/Documentation/CodingStyle . Just like object-oriented programming is emulated using structs populated with function pointers, goto has its place in C programming. So who is right: Dijkstra or Linus (and all Linux kernel coders)? It’s theory vs. practice basically.

There is however the usual gotcha for not having compiler-level support and checks for common constructs/patterns: it’s easier to use them wrong and introduce bugs without compile-time checks. Windows and Visual C++ but in C mode offer exception handling via SEH/VEH for this very reason: exceptions are useful even outside OOP languages, ie in a procedural language. But the compiler can’t always save your bacon, even if it offers syntactic support for exceptions in the language. Consider as example of the latter case the famous Apple SSL “goto fail” bug, which just duplicated one goto with disastrous consequences ( 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 

You can have exactly the same bug using compiler-supported exceptions, eg in C++:

 struct Fail {}; try { if (something()) throw Fail(); throw Fail(); // copypasta bug printf("Never reached\n"); } catch (Fail&) { // control jumps here } 

But both variants of the bug can be avoided if the compiler analyzes and warns you about unreachable code. For example compiling with Visual C++ at the /W4 warning level finds the bug in both cases. Java for instance forbids unreachable code (where it can find it!) for a pretty good reason: it’s likely to be a bug in the average Joe’s code. As long as the goto construct doesn’t allow targets that the compiler can’t easily figure out, like gotos to computed addresses(**), it’s not any harder for the compiler to find unreachable code inside a function with gotos than using Dijkstra-approved code.

(**) Footnote: Gotos to computed line numbers are possible in some versions of Basic, eg GOTO 10*x where x is a variable. Rather confusingly, in Fortran “computed goto” refers to a construct that is equivalent to a switch statement in C. Standard C doesn’t allow computed gotos in the language, but only gotos to statically/syntactically declared labels. GNU C however has an extension to get the address of a label (the unary, prefix && operator) and also allows a goto to a variable of type void*. See https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html for more on this obscure sub-topic. The rest of this post ins’t concerned with that obscure GNU C feature.

Standard C (ie not computed) gotos are not usually the reason why unreachable code can’t be found at compile time. The usual reason is logic code like the following. Данный

 int computation1() { return 1; } int computation2() { return computation1(); } 

It’s just as hard for a compiler to find unreachable code in any of the following 3 constructs:

 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&) { } } 

(Excuse my brace-related coding style, but I tried to keep the examples as compact as possible.)

Visual C++ /W4 (even with /Ox) fails to find unreachable code in any of these, and as you probably know the problem of finding unreachable code is undecidable in general. (If you don’t believe me about that: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )

As a related issue, the C goto can be used to emulate exceptions only inside the body of a function. The standard C library offers a setjmp() and longjmp() pair of functions for emulating non-local exits/exceptions, but those have some serious drawbacks compared to what other languages offer. The Wikipedia article http://en.wikipedia.org/wiki/Setjmp.h explains fairly well this latter issue. This function pair also works on Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), but hardly anyone uses them there because SEH/VEH is superior. Even on Unix, I think setjmp and longjmp are very seldom used.

2) I think the second most common use of goto in C is implementing multi-level break or multi-level continue, which is also a fairly uncontroversial use case. Recall that Java doesn’t allow goto label, but allows break label or continue label. According to http://www.oracle.com/technetwork/java/simple-142616.html , this is actually the most common use case of gotos in C (90% they say), but in my subjective experience, system code tends to use gotos for error handling more often. Perhaps in scientific code or where the OS offers exception handling (Windows) then multi-level exits are the dominant use case. They don’t really give any details as to the context of their survey.

Edited to add: it turns out these two use patterns are found in the C book of Kernighan and Ritchie, around page 60 (depending on edition). Another thing of note is that both use cases involve only forward gotos. And it turns out that MISRA C 2012 edition (unlike the 2004 edition) now permits gotos, as long as they are only forward ones.

In Perl, use of a label to “goto” from a loop – using a “last” statement, which is similar to break.

This allows better control over nested loops.

The traditional goto label is supported too, but I’m not sure there are too many instances where this is the only way to achieve what you want – subroutines and loops should suffice for most cases.

The problem with ‘goto’ and the most important argument of the ‘goto-less programming’ movement is, that if you use it too frequently your code, although it might behave correctly, becomes unreadable, unmaintainable, unreviewable etc. In 99.99% of the cases ‘goto’ leads to spaghetti code. Personally, I cannot think of any good reason as to why I would use ‘goto’.

Edsger Dijkstra, a computer scientist that had major contributions on the field, was also famous for criticizing the use of GoTo. There’s a short article about his argument on Wikipedia .

I use goto in the following case: when needed to return from funcions at different places, and before return some uninitialization needs to be done:

non-goto version:

 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 version:

 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; } 

The second version makes it easier, when you need to change something in the deallocation statements (each is used once in the code), and reduces the chance to skip any of them, when adding a new branch. Moving them in a function will not help here, because the deallocation can be done at different “levels”.

Some say there is no reason for goto in C++. Some say that in 99% cases there are better alternatives. To be concrete, here’s an example where goto leads to a nice code, something like enhanced do-while loop:

 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; 

Compare it to 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; 

I see these differences:

  • nested {} block is needed (albeit do {...} while looks more familiar)
  • extra loop variable is needed, used in four places
  • it takes longer time to read and understand the work with the loop
  • the loop does not hold any data, it just controls the flow of the execution, which is less comprehensible than simple label

The point is that goto can be easily misused, but goto itself is not to blame. Note that label has function scope in C++, so it does not pollute global scope like in pure assembly, in which overlapping loops has its place and are very common - like in the following code for 8051, where 7segment display is connected to P1. The program loops lightning segment around:

 ; 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 
  • Является ли GUID уникальным в 100% случаев?
  • Почему бы не использовать исключения как регулярный stream контроля?
  • Аллен Голуб писал: «Вы никогда не должны использовать функции get / set», правильно ли он?
  • Есть ли алгоритм для смешивания цветов, который работает как смешивание реальных цветов?
  • Почему NaN не равен NaN?
  • Генерация перетасованного диапазона с использованием PRNG, а не перетасовка
  • Уникальные (не повторяющиеся) случайные числа в O (1)?
  • Когда бросать исключение?
  • Должен ли я тестировать частные методы или только публичные?
  • Почему переменные «i» и «j» используются для счетчиков?
  • Можете ли вы объяснить шаблон дизайна контекста?
  • Interesting Posts

    Почему URE отказывается от восстановления рейда и «делает RAID 5 непригодным для использования»

    java: как я могу выполнять динамическое кастинг переменной из одного типа в другой?

    Изменение значков узла JTree в соответствии с уровнем глубины

    Мой жесткий диск был заведомо перезаписан двумя плохими разделами

    Служба Android останавливается, когда приложение закрыто

    Как я могу настроить фокус (и отображать клавиатуру) на моем EditText программно

    Кнопки «Назад / Вперед» не работают в гостевой ОС VMWare Workstation 6.5

    В чем разница между vmalloc и kmalloc?

    Изменение DPI на OSX

    Выберите элемент, когда имя classа начинается с определенного слова

    Является ли RAID-контроллер столь же легко замененным, как и диск?

    Какие кодеки наиболее подходят для воспроизведения с помощью Windows Media Player в Windows XP?

    Как узнать учетную запись пользователя SID для Windows?

    Нужны ли параметры SVG, такие как «xmlns» и «версия»?

    Как остановить обновление окон?

    Давайте будем гением компьютера.