Вопрос:

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

c exception-handling language-agnostic goto

44504 просмотра

24 ответа

7238 Репутация автора

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

Автор: Landon Источник Размещён: 23.08.2008 06:18

Ответы (24)


1 плюс

2330 Репутация автора

В Perl использование метки для «перехода» из цикла - с использованием «последней» инструкции, которая похожа на break.

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

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

Автор: Abhinav Размещён: 23.08.2008 06:22

1 плюс

7987 Репутация автора

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

Автор: cschol Размещён: 23.08.2008 06:30

6 плюса

359 Репутация автора

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

Автор: Ari Pernick Размещён: 23.08.2008 06:30

37 плюса

399991 Репутация автора

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

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

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

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

Помимо этого, gotoэто признак того, что недостаточно внимания было уделено конкретному коду.


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

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

Автор: Konrad Rudolph Размещён: 23.08.2008 06:40

226 плюса

10381 Репутация автора

Решение

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

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

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

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

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

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

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

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

Автор: Chris Gillum Размещён: 23.08.2008 06:42

27 плюса

21458 Репутация автора

В операторе C # switch не допускается провал . Таким образом, 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;
}

Редактировать: есть одно исключение из правила "без провала". Прорыв разрешен, если в регистре нет кода.

Автор: Jakub Šturc Размещён: 23.08.2008 07:10

2 плюса

14889 Репутация автора

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

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

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

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

Автор: DrPizza Размещён: 23.08.2008 07:14

6 плюса

3887 Репутация автора

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

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

Это создало бы причудливую, но, возможно, легальную структуру управления потоком, где возможна последовательность типа (a, b, c, b, a, b, a, b, ...), что делает хакеров компиляторов несчастными. Очевидно, есть ряд хитрых приемов оптимизации, которые основаны на том, что такого типа структуры не существует. (Я должен проверить свою копию книги о драконах ...) В результате (при использовании некоторых компиляторов) это может привести к тому, что другие оптимизации не будут выполнены для кода, содержащего gotos.

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

Автор: Anders Eurenius Размещён: 23.08.2008 07:44

14 плюса

183171 Репутация автора

#ifdef TONGUE_IN_CHEEK

В Perl есть функция goto, позволяющая вам выполнять хвостовые вызовы бедняков. :-П

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

#endif

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

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

Автор: Chris Jester-Young Размещён: 25.08.2008 02:53

1 плюс

2205 Репутация автора

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

Автор: bubbassauro Размещён: 25.08.2008 03:44

3 плюса

25531 Репутация автора

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

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немного кода:

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

Или перейдем к lastфрагменту кода из нескольких мест:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}
Автор: Brad Gilbert Размещён: 16.09.2008 04:43

2 плюса

2255 Репутация автора

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

Автор: Ken Ray Размещён: 16.09.2008 04:48

12 плюса

4265 Репутация автора

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

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

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

Автор: bugmagnet Размещён: 16.09.2008 05:25

146 плюса

45300 Репутация автора

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

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

Автор: dsimcha Размещён: 02.02.2010 07:59

7 плюса

87 Репутация автора

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

Автор: Ricardo Размещён: 11.05.2010 08:54

34 плюса

8599 Репутация автора

Ну, есть одна вещь, которая всегда хуже 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
Автор: Viktor Sehr Размещён: 11.05.2010 09:06

885 плюса

28514 Репутация автора

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

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

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

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

Я уже слышу поднятые крики культистов, когда они сталкиваются с еретиком. «Но, - будут повторять они, - вы можете сделать код, с которым очень трудно читать 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.
Автор: JUST MY correct OPINION Размещён: 11.05.2010 09:53

2 плюса

41 Репутация автора

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

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

for (stepfailed=0 ; ! stepfailed ; /*empty*/)
Автор: Sandy Размещён: 11.05.2010 12:09

2 плюса

466 Репутация автора

Конечно, GOTO можно использовать, но есть еще одна важная вещь, чем стиль кода, или, если код является или не читаемым, о чем вы должны помнить при его использовании: код внутри может быть не таким надежным, как вы. думаю .

Например, посмотрите на следующие два фрагмента кода:

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

Эквивалентный код с GOTO

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

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

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

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

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

Связанные материалы можно найти в известной статье г-на Дейкстры «Дело против заявления GO TO»

Автор: pocjoc Размещён: 04.01.2012 12:35

1 плюс

669 Репутация автора

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

не-goto версия:

int doSomething (struct my_complicated_stuff *ctx)    
{
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
        db_disconnect(conn);
        return -1;      
        }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        free(temp_data);    
        db_disconnect(conn);    
        return -2;
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        pthread_mutex_unlock(ctx->mutex);
        free(temp_data);
        db_disconnect(conn);        
        return -3;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -4;  
    }

    if (ctx->something_else->additional_check) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -5;  
    }


    pthread_mutex_unlock(ctx->mutex);
    free(temp_data);    
    db_disconnect(conn);    
    return 0;     
}

Перейти к версии:

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

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

Автор: Nuclear Размещён: 19.11.2013 08:45

4 плюса

86 Репутация автора

Наиболее вдумчивое и тщательное обсуждение операторов goto, их законного использования и альтернативных конструкций, которые могут использоваться вместо «добродетельных утверждений goto», но которыми можно злоупотреблять так же легко, как и заявлениями goto, - это статья Дональда Кнута « Структурное программирование с утверждениями goto » , в декабре 1974 года, «Компьютерные обзоры» (том 6, № 4, с. 261 - 301).

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

Автор: nhcohen Размещён: 02.01.2014 04:44

8 плюса

37166 Репутация автора

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

Автор: herohuyongtao Размещён: 26.01.2014 05:29

2 плюса

3156 Репутация автора

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

Тем не менее, существует обычная ошибка, связанная с отсутствием поддержки на уровне компилятора и проверками общих конструкций / шаблонов: проще использовать их неправильно и вводить ошибки без проверок во время компиляции. 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 - переменная. Весьма странно, что в Фортране «computed goto» относится к конструкции, которая эквивалентна выражению switch в C. Стандартный C не допускает вычисляемые goto в языке, а только goto для статически / синтаксически объявленных меток. GNU C, однако, имеет расширение для получения адреса метки (унарный оператор, префикс && оператор), а также позволяет перейти к переменной типа void *. См. Https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html для получения дополнительной информации по этой неясной подтеме. Остальная часть этого поста не связана с этой неясной функцией GNU C.

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

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

Компилятору так же трудно найти недоступный код в любой из следующих трех конструкций:

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 (в зависимости от издания). Также следует отметить, что в обоих случаях используются только прямые переходы. И оказывается, что выпуск MISRA C 2012 (в отличие от выпуска 2004 г.) теперь разрешает goto, если они только передовые.

Автор: Fizz Размещён: 14.07.2014 06:42

1 плюс

18459 Репутация автора

Некоторые говорят, что нет причин для перехода в 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;

Сравните это с свободным кодом:

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

Теперь давайте избавимся от «злого» гото:

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

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

Дело в том, что goto можно легко использовать неправильно, но само goto не виновато. Обратите внимание, что метка имеет область видимости функции в C ++, поэтому она не загрязняет глобальную область видимости, как в чистой сборке, в которой перекрывающиеся циклы имеют свое место и очень распространены - как в следующем коде для 8051, где 7-сегментный дисплей подключен к 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)

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

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;
Автор: Jan Turoň Размещён: 13.10.2016 10:08
Вопросы из категории :
32x32