Выгодно ли когда-либо использовать "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 В С." Да? Вы можете сделать код очень трудным для чтения без 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!

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

  1. Дейкстры бумага на goto операторы были написаны для среды программирования, где goto был много более потенциально опасно, чем в большинстве современных языков, которые не являются ассемблером.
  2. автоматически выбрасывая все виды использования goto из-за этого примерно так же разумно, как сказать: "я пытался повеселиться один раз, но не понравилось, так что теперь я против".
  3. есть законное использование современных (анемичных)goto операторы в коде, которые не могут быть адекватно заменен другими конструкциями.
  4. есть, конечно, незаконное использование одних и тех же утверждений.
  5. есть также незаконное использование современных управляющих операторов, таких как"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,...) возможно, что делает хакеров компилятора несчастными. По-видимому, есть ряд умных оптимизационных трюков, которые полагаются на этот тип структуры не происходит. (Я должен проверить книгу дракон...) Результатом этого может быть (с использованием некоторых компиляторов) то, что другие оптимизации не выполняются для кода, содержащего gotos.

Это может быть полезно, если вы знаю это просто, "о, кстати", случается, чтобы убедить компилятор испускать более быстрый код. Лично я предпочел бы попытаться объяснить компилятору о том, что вероятно, а что нет, прежде чем использовать трюк, такой как 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 лучше сделать с другие ключевые слова.

как redoing немного кода:

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


Так же хорошо, что никто никогда не реализовал оператор "COME FROM"....


Я нахожу использование 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