Сведения о вопросе

Killer

04:33, 26th August, 2020

Теги

Выгодно ли вообще использовать 'goto' в языке, поддерживающем циклы и функции? Если да, то почему?

Просмотров: 508   Ответов: 24

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



  Сведения об ответе

DO__IT

17:58, 15th August, 2020

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

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

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

THIS - это "the unbridled use of the go to statement", против которого Дейкстра выступал в своей статье в 1968 году. THIS -это среда, в которой он жил, которая привела его к написанию этой статьи. Способность прыгать в любом месте вашего кода в любой момент, который вам нравится, была тем, что он критиковал и требовал, чтобы его остановили. Сравнение этого с анемичными способностями goto в C или других таких же более современных языках просто смехотворно.

Я уже слышу громкие песнопения сектантов, обращенных к еретику. "But," они будут скандировать: "вы можете сделать код очень трудным для чтения с 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 !

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

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


  Сведения об ответе

ASSembler

21:06, 1st October, 2020

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

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

Часто в функции можно выделить ресурсы и выйти из нее в нескольких местах. Программисты могут упростить свой код, поместив код очистки ресурсов в конце функции,и все "exit points" функции получат метку очистки. Таким образом, вам не нужно писать код очистки на каждом "exit point" функции.

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

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

Повышение производительности на низком уровне

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

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


  Сведения об ответе

ASER

19:07, 20th August, 2020

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

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


  Сведения об ответе

PAGE

00:21, 27th August, 2020

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

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

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

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

Кроме того, goto -это признак того, что в конкретный фрагмент кода было вложено недостаточно мыслей.


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

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


  Сведения об ответе

dumai

14:41, 4th August, 2020

Ну, есть одна вещь, которая всегда хуже, чем 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


  Сведения об ответе

KOMP

21:06, 1st October, 2020

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

Edit: есть одно исключение из правила "no fall-through". Провал допустим, если оператор case не имеет кода.


  Сведения об ответе

COOL

07:09, 4th August, 2020

#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

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

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


  Сведения об ответе

nYU

12:46, 2nd August, 2020

За эти годы я написал больше, чем несколько строк на языке assembly. В конечном счете, каждый язык высокого уровня компилируется вплоть до gotos. Ладно, назовем их "branches" или "jumps" или как там еще, но они готы. Может ли кто-нибудь написать goto-less ассемблер?

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

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


  Сведения об ответе

ASSembler

09:31, 4th August, 2020

Посмотрите, когда следует использовать Goto при программировании в C :

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

Большая часть того, что я должен сказать о Гото, действительно относится только к C. Если вы используете C++, нет никакой разумной причины использовать goto вместо исключений. В C, однако, у вас нет возможности механизма обработки исключений, поэтому если вы хотите отделить обработку ошибок от rest логики вашей программы, и вы хотите избежать перезаписи очищенного кода несколько раз по всему коду, то 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;
}

Преимущество здесь заключается в том, что ваш код following end имеет доступ ко всему, что ему потребуется для выполнения очистки, и вам удалось значительно сократить количество точек изменения. Еще одно преимущество заключается в том, что вы перешли от нескольких точек выхода для своей функции к одной; нет никакого шанса, что вы случайно вернетесь из функции, не очистив ее.

Кроме того, поскольку goto используется только для перехода к одной точке, это не значит, что вы создаете массу спагетти-кода, прыгающего туда и обратно в попытке имитировать вызовы функций. Скорее, Гото действительно помогает писать более структурированный код.


Одним словом, goto всегда следует использовать скупо и в крайнем случае-но для этого есть время и место. Вопрос должен быть не "do you have to use it", а "is it the best choice", чтобы использовать его.


  Сведения об ответе

ITSME

11:51, 2nd August, 2020

Я нахожу забавным, что некоторые люди заходят так далеко, чтобы дать список случаев, когда goto является приемлемым, говоря, что все другие виды использования неприемлемы. Вы действительно думаете, что знаете каждый случай, когда goto является лучшим выбором для выражения алгоритма?

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

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

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


  Сведения об ответе

SEEYOU

09:43, 3rd August, 2020

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

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

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

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


  Сведения об ответе

SILA

11:35, 15th August, 2020

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


  Сведения об ответе

screen

23:36, 8th August, 2020

Наиболее вдумчивым и тщательным обсуждением утверждений goto, их законного использования и альтернативных конструкций, которые могут быть использованы вместо "virtuous goto statements", но могут быть использованы так же легко, как и утверждения goto, является статья Дональда Кнута " структурированное Программирование с утверждениями goto ", опубликованная в декабре 1974 года в журнале Computing Surveys (volume 6, no. 4. С. 261-301).

Неудивительно, что некоторые аспекты этой 39-летней работы датированы: Orders-of-magnitude увеличение вычислительной мощности делает некоторые улучшения производительности кнута незаметными для задач среднего размера, и с тех пор были изобретены новые конструкции языка программирования. (Например, блоки try-catch включают в себя конструкцию Zahn, хотя они редко используются таким образом.) Но кнут охватывает все стороны спора, и его следует обязать прочитать, прежде чем кто-либо еще раз повторит этот вопрос.


  Сведения об ответе

$DOLLAR

03:30, 9th August, 2020

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

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

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

  Сведения об ответе

P_S_S

13:16, 12th August, 2020

Я нахожу использование do{} while(ложное) совершенно отвратительным. Вполне возможно, что он может убедить меня в необходимости этого в каком-то странном случае, но никогда не в том, что это чистый разумный код.

Если вы должны сделать какой-то такой цикл, почему бы не сделать зависимость от переменной флага явной?

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


  Сведения об ответе

SKY

08:56, 19th August, 2020

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

C не имеет разрыва multi-level/labelled, и не все потоки управления можно легко смоделировать с помощью примитивов итерации и принятия решений C. готы проделывают большой путь к исправлению этих недостатков.

Иногда более понятно использовать какую-то переменную флага для достижения своего рода разрыва pseudo-multi-level, но она не всегда превосходит goto (по крайней мере, goto позволяет легко определить, куда идет управление, в отличие от переменной флага), а иногда вы просто не хотите платить цену производительности флагов/других искажений, чтобы избежать goto.

libavcodec-это чувствительный к производительности фрагмент кода. Прямое выражение потока управления, вероятно, является приоритетом, потому что он будет иметь тенденцию работать лучше.


  Сведения об ответе

qwerty101

13:22, 17th August, 2020

Точно так же никто никогда не реализовывал утверждение "COME FROM"....


  Сведения об ответе

park

10:40, 10th August, 2020

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)

Первое, что мы думаем, что результатом обоих битов кода будет то, что "Value of A: 0" (мы предполагаем выполнение без параллелизма, конечно)

Это неверно: в первом примере A всегда будет 0, но во втором примере (с оператором GOTO) A может и не быть 0. Почему?

Причина в том, что из другой точки программы я могу вставить a GOTO FINAL , не контролируя значение A.

Этот пример очень очевиден, но по мере усложнения программ сложность восприятия подобных вещей возрастает.

Соответствующий материал можно найти в знаменитой статье г-на Дейкстры "A case against the GO TO statement"


  Сведения об ответе

прога

16:16, 8th August, 2020

1) наиболее распространенное использование goto, о котором я знаю, - это эмуляция обработки исключений в языках, которые ее не предлагают, а именно в C. (Код, приведенный выше Nuclear, именно таков.) Посмотрите на исходный код Linux, и вы увидите, что таким образом используется базиллион goto; согласно быстрому опросу, проведенному в 2013 году, в коде Linux было около 100 000 goto: http://blog.regehr.org/archives/894 . Использование Goto даже упоминается в руководстве по стилю кодирования Linux: https://www.kernel.org/doc/Documentation/CodingStyle . Точно так же, как объектно-ориентированное программирование эмулируется с помощью структур, заполненных указателями функций, goto имеет свое место в программировании C. Так кто же прав: Дийкстра или Лайнус (и все Linux kernel кодеры)? В основном это теория против практики.

Существует, однако, обычная Гота для того, чтобы не иметь поддержки на уровне компилятора и проверки для общего constructs/patterns: проще использовать их неправильно и вводить ошибки без проверок во время компиляции. Windows и Visual C++, но в режиме C предлагают обработку исключений через SEH/VEH именно по этой причине: исключения полезны даже за пределами языков OOP, т. е. в процедурном языке. Но компилятор не всегда может сохранить ваш бекон, даже если он предлагает синтаксическую поддержку исключений в языке. Рассмотрим в качестве примера последнего случая знаменитый баг Apple SSL "goto fail", который просто дублировал один goto с катастрофическими последствиями ( https://www.imperialviolet.org/2014/02/22/applebug.html ):

if (something())
  goto fail;
  goto fail; // copypasta bug
printf("Never reached\n");
fail:
  // control jumps here

Вы можете иметь точно такую же ошибку, используя исключения, поддерживаемые компилятором, например, в C++:

struct Fail {};

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

Но оба варианта ошибки можно избежать, если компилятор проанализирует и предупредит вас о недостижимом коде. Например, компиляция с Visual C++ на уровне предупреждения /W4 обнаруживает ошибку в обоих случаях. Java например запрещает недостижимый код (где он может его найти!) по довольно веской причине: это, скорее всего, ошибка в коде среднего Джо. До тех пор, пока конструкция goto не допускает цели, которые компилятор не может легко вычислить, например goto для вычисляемых адресов (**), компилятору не сложнее найти недостижимый код внутри функции с goto, чем с помощью кода, одобренного Dijkstra.

( * * ) Сноска: Gotos к вычисляемым номерам строк возможны в некоторых версиях Basic, например GOTO 10*x, где x-переменная. Довольно запутанно, in Fortran "computed goto" ссылается на конструкцию, которая эквивалентна оператору switch в C. Стандарт C не позволяет вычислять gotos в языке,но только gotos для статически / синтаксически объявленных меток. Однако GNU C имеет расширение для получения адреса метки (унарный, префиксный оператор &&), а также позволяет перейти к переменной типа void*. подробнее об этом неясном подтеме см. https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html. rest этого поста не касается этой непонятной функции GNU C.

Стандартные C (т. е. не вычисленные) goto обычно не являются причиной того, что недостижимый код не может быть найден во время компиляции. Обычная причина-это логический код, подобный следующему. Дано

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() для эмуляции нелокальных exits/exceptions,, но у них есть некоторые серьезные недостатки по сравнению с тем, что предлагают другие языки. Статья Википедии 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 для обработки ошибок. Возможно, в научном коде или там, где OS предлагает обработку исключений (Windows), тогда многоуровневые выходы являются доминирующим вариантом использования. Они действительно не дают никаких подробностей относительно контекста своего опроса.

Отредактировано, чтобы добавить: оказывается, эти два шаблона использования находятся в книге C Кернигана и Ричи, около страницы 60 (в зависимости от издания). Другое дело, что оба варианта использования включают только форвард goto. И получается, что MISRA C 2012 edition (в отличие от издания 2004 года) теперь разрешает goto, пока они являются только передовыми.


  Сведения об ответе

SSESION

14:58, 19th August, 2020

В Perl используется метка к "goto" из цикла-с помощью оператора "last", который аналогичен break.

Это позволяет лучше контролировать вложенные циклы.

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


  Сведения об ответе

#hash

22:14, 28th August, 2020

Проблема с 'goto' и самым важным аргументом движения "Гото-менее Программирование" заключается в том, что если вы используете его слишком часто, ваш код, хотя он может вести себя правильно, становится нечитаемым, недостижимым, недоступным для просмотра и т. д. В 99.99% случаях 'goto' приводит к спагетти-коду. Лично я не могу придумать ни одной веской причины, по которой я бы использовал 'goto'.


  Сведения об ответе

COOL

18:08, 27th August, 2020

Эдсгер Дейкстра, ученый-компьютерщик, внесший большой вклад в эту область, также был известен критикой использования GoTo. В Википедии есть короткая статья о его аргументации .


  Сведения об ответе

park

08:41, 29th August, 2020

Я использую 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;     
}

версия Гото:

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

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


  Сведения об ответе

KOMP

11:27, 21st August, 2020

Некоторые говорят, что нет никакой причины для Гото в C++. Некоторые говорят, что в 99% случаях есть лучшие альтернативы. Это не рассуждение, а просто иррациональные впечатления. Вот солидный пример, где goto приводит к хорошему коду, что-то вроде расширенного цикла do-while:

int i;

PROMPT_INSERT_NUMBER:
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    goto PROMPT_INSERT_NUMBER;          
  }

std::cout << "your number is " << i;

Сравните его с goto-free кодом:

int i;

bool loop;
do {
  loop = false;
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    loop = true;          
  }
} while(loop);

std::cout << "your number is " << i;

Я вижу эти различия:

  • вложенный блок {} необходим (хотя do {...} while выглядит более знакомым)
  • необходима дополнительная переменная loop , используемая в четырех местах
  • требуется больше времени, чтобы прочитать и понять работу с loop
  • loop не содержит никаких данных, он просто управляет потоком выполнения, что менее понятно, чем простая метка

Есть и другой пример

void sort(int* array, int length) {
SORT:
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    goto SORT; // it is very easy to understand this code, right?
  }
}

А теперь давайте избавимся от "evil" Гото:

void sort(int* array, int length) {
  bool seemslegit;
  do {
    seemslegit = true;
    for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
      swap(data[i], data[i+1]);
      seemslegit = false;
    }
  } while(!seemslegit);
}

Вы видите, что это тот же самый тип использования goto, это хорошо структурированный шаблон, и это не вперед goto так много продвигают, как единственный рекомендуемый способ. Конечно, вы хотите избежать "smart" кода, как это:

void sort(int* array, int length) {
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    i = -1; // it works, but WTF on the first glance
  }
}

Дело в том, что Гото можно легко использовать не по назначению, но сам Гото не виноват. Обратите внимание, что метка имеет область действия в C++, поэтому она не загрязняет глобальную область, как в pure assembly, в которой перекрывающиеся циклы имеют свое место и очень распространены - как в следующем коде для 8051, где 7segment display подключен к P1. Программа обводит молниеносный сегмент по кругу:

; P1 states loops
; 11111110 <-
; 11111101  |
; 11111011  |
; 11110111  |
; 11101111  |
; 11011111  |
; |_________|

init_roll_state:
    MOV P1,#11111110b
    ACALL delay
next_roll_state:
    MOV A,P1
    RL A
    MOV P1,A
    ACALL delay
    JNB P1.5, init_roll_state
    SJMP next_roll_state

Есть еще одно преимущество: goto может служить в качестве именованных циклов, условий и других потоков:

if(valid) {
  do { // while(loop)

// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket

  } while(loop);
} // if(valid)

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

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться