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

201

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

Приземляться
источник

Ответы:

242

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

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

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

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

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

Улучшения на низком уровне производительности

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

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

Крис Гиллум
источник
15
Правильный способ выхода из вложенных циклов - это рефакторинг внутреннего цикла в отдельный метод.
Джейсон
129
@ Джейсон - Бах. Это груз быка. Замена gotoна returnэто просто глупо. Это не «рефакторинг» ничего, это просто «переименование», чтобы люди, выросшие в gotoподавленной среде (то есть все мы), чувствовали себя лучше при использовании того, что морально равнозначно a goto. Я предпочитаю видеть цикл, в котором я его использую, и видеть немного goto, который сам по себе является просто инструментом , чем видеть, как кто-то переместил цикл куда-то, не связанное только для того, чтобы избежать goto.
Крис Латс
18
Существуют ситуации, где gotos важны: например, среда C ++ без исключений. В источнике Silverlight у нас есть десятки тысяч (или более) операторов goto для безопасной функции с помощью макросов - ключевые медиа-кодеки и библиотеки часто работают с возвращаемыми значениями, а не с исключениями, и эти механизмы обработки ошибок трудно объединить в единственный эффективный способ.
Джефф Уилкокс
79
Стоит отметить , что все break, continue, returnв основном goto, только в красивой упаковке.
el.pescado
16
Я не понимаю, как do{....}while(0)должна быть лучшая идея, чем goto, за исключением того факта, что она работает в Java.
Джереми Лист
906

Все, кто 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.
ПРОСТО МОЕ правильное мнение
источник
42
@pocjoc: например, написание хвостовой рекурсивной оптимизации на C. Это встречается в реализации функциональных интерпретаторов языка / сред выполнения, но представляет интересную категорию проблем. По этой причине большинство компиляторов, написанных на C, используют goto. Его использование ниши , которая не встречается в большинстве случаев, конечно, но в ситуациях , это называется для это делает много смысла для использования.
zxq9
66
Почему все говорят о работе Дейкстры «Идти к опасным» без упоминания ответа Кнута «Структурированное программирование с утверждениями»?
Обломов
22
@ Nietzche-jou "это довольно вопиющий соломенный человек [...]" Он саркастически использует ложную дихотомию, чтобы показать, почему стигма нелогична. Strawman - это логическая ошибка, когда вы намеренно искажаете позицию противника, чтобы создать впечатление, что его позиция легко побеждена. Например, «Атеисты просто атеисты, потому что они ненавидят Бога». Ложная дихотомия - это когда вы предполагаете, что противоположная крайность верна, когда существует хотя бы одна дополнительная возможность (то есть, черно-белая). Например, «Бог ненавидит гомосексуалистов, поэтому он любит гетеросексуалов».
Брэден Бест
27
@JLRishe Ты читаешь слишком глубоко во что-то, чего даже нет. Это - только истинная логическая ошибка, если человек, который использовал это честно, полагает, что это имеет логический смысл. Но он сказал это с сарказмом, ясно показывая, что он знает, что это смешно, и вот как он это показывает. Он не «использует это, чтобы оправдать свою точку зрения»; он использует это, чтобы показать, почему смешно говорить, что gotos автоматически превращает код в спагетти. Т.е. вы все еще можете написать дерьмовый код без gotos. Это его точка зрения. И саркастическая ложная дихотомия - его метод с юмористической иллюстрацией.
Брэден Бест
32
-1 за очень большой аргумент, почему замечание Дейкстры не применимо, и в то же время забывает показать, в чем на gotoсамом деле преимущества (и это вопрос)
Мартен Бодьюс
154

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

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

dsimcha
источник
36

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

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

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

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

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


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

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

Конрад Рудольф
источник
4
Просто любопытно, но как насчет случая использования gotos для очистки кода. Под очисткой я подразумеваю не только освобождение памяти, но и, скажем, ведение журнала ошибок. Я читал кучу сообщений, и, видимо, никто не пишет код, который печатает журналы .. хм ?!
Шива
37
«В основном, из-за дефектного характера goto (и я считаю, что это не вызывает сомнений)» -1, и я перестал читать там.
о0 '.
14
Предостережение против goto исходит из эпохи структурированного программирования, когда ранние возвращения также считались злом. Перемещение вложенных циклов в функцию обменивает одно «зло» на другое и создает функцию, у которой нет реальной причины существования (за исключением «это позволило мне избежать использования goto!»). Это правда, что goto - это самый мощный инструмент для создания кода для спагетти, если он используется без ограничений, но это идеальное приложение для него; это упрощает код. Кнут спорил об этом, и Дейкстра сказал: «Не попадитесь в ловушку веры в то, что я ужасно догматичен по поводу Гото».
Грязь
5
finally? Таким образом, использование исключений для других вещей, кроме обработки ошибок, хорошо, а использование goto- плохо? Я думаю, что исключения довольно удачно названы.
Кристиан
3
@Mud: в случаях, с которыми я сталкиваюсь (ежедневно), создание функции «у которой нет реальной причины существования» является лучшим решением. Причина: он удаляет детали из функции верхнего уровня, так что результат имеет легко читаемый поток управления. Лично я не сталкиваюсь с ситуациями, когда goto создает наиболее читаемый код. С другой стороны, я использую несколько возвратов в простых функциях, когда кто-то другой может предпочесть перейти к одной точке выхода. Я хотел бы видеть контрпримеры, где goto более читабелен, чем рефакторинг в хорошо названные функции.
ToolmakerSteve
35

Ну, есть одна вещь, которая всегда хуже 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
Виктор Сер
источник
2
do{}while(false)Я думаю, что можно считать идиоматическим. Вы не можете не соглашаться: D
Томас Эдинг
37
@trinithis: Если это «идиоматический», то это только из-за культа анти-гото. Если вы внимательно посмотрите на это, вы поймете, что это просто способ сказать, goto after_do_block;фактически не говоря этого. Иначе ... «цикл», который запускается ровно один раз? Я бы назвал это злоупотреблением контрольными структурами.
cHao
5
@ThomasEding Eding Есть одно исключение из вашей точки зрения. Если вы когда-нибудь занимались программированием на C / C ++ и должны были использовать #define, вы бы знали, что do {} while (0) является одним из стандартов для инкапсуляции нескольких строк кода. Например: #define do {memcpy (a, b, 1); что-то ++;} while (0) безопаснее и лучше, чем #define memcpy (a, b, 1); что-то ++
Ignas2526
10
@ Ignas2526 Вы просто очень хорошо показали, как #defineмного, много раз намного хуже, чем gotoвремя от времени: D
Luaan
3
+1 за хороший список способов, которыми люди пытаются избежать gotos, до крайности; Я видел упоминания о таких методах в других местах, но это отличный, сжатый список. Размышляя о том, почему за последние 15 лет моей команде никогда не требовался ни один из этих методов, ни использовался gotos. Даже в клиент-серверном приложении с сотнями тысяч строк кода несколькими программистами. C #, Java, PHP, Python, JavaScript. Код клиента, сервера и приложения. Не хвастаться и не заниматься прозелитизмом на одну должность, просто по-настоящему любопытно, почему некоторые люди сталкиваются с ситуациями, когда умоляют gotos как самое ясное решение, а другие нет ...
ToolmakerSteve
28

В операторе 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;
}

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

Якуб Штурц
источник
3
Переключение между коммутаторами поддерживается в .NET 2.0 - msdn.microsoft.com/en-us/library/06tc147t(VS.80).aspx
rjzii
9
Только если дело не имеет тела кода. Если у него есть код, вы должны использовать ключевое слово goto.
Мэтью Уайтед
26
Этот ответ очень забавный - C # убрал провал, потому что многие считают его вредным, и этот пример использует goto (также многие считают его вредным), чтобы восстановить исходное, предположительно вредное поведение, НО общий результат на самом деле менее вреден ( потому что код проясняет, что падение является преднамеренным!).
Томасруттер
9
Только потому, что ключевое слово написано буквами GOTO, это не значит, что это goto. Этот представленный случай не является goto. Это конструкция оператора switch для прорыва. С другой стороны, я не очень хорошо знаю C #, поэтому могу ошибаться.
Томас Эдинг
1
Ну, в этом случае это немного больше, чем провал (потому что вы можете сказать, goto case 5:когда вы в случае 1). Похоже, что ответ Конрада Рудольфа здесь правильный: gotoон компенсирует отсутствующую функцию (и менее ясен, чем реальная функция). Если то, что мы действительно хотим, - это провал, возможно, лучшим вариантом по умолчанию будет не провал, а что-то вроде continueявного запроса.
Дэвид Стоун
14

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

Крис Шут-Янг
источник
Я думаю, что это единственная причина использовать goto в Perl.
Брэд Гилберт
12

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

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

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

bugmagnet
источник
1
Возможно, вы хотите прочитать, почему я считаю, что это в корне неправильно на stackoverflow.com/questions/46586/…
Конрад Рудольф
15
Любой, кто продвигает "все это сводится к JMP в любом случае!" аргумент в основном не понимает смысла программирования на языке более высокого уровня.
Ницше Джоу
7
Все, что вам действительно нужно, это вычитать и ветвь. Все остальное для удобства или производительности.
Дэвид Стоун
8

Посмотрите, когда использовать 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
источник
7

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

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

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

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

Андерс Эурениус
источник
3
Ну ... возвращает меня ко дням программирования на Фортране в финансовой информационной компании. В 2007 г.
Марчин
5
Любая языковая структура может быть нарушена способами, которые делают ее нечитаемой или неэффективной.
ПРОСТО МОЕ правильное мнение
@ ПРОСТО: Дело не в удобочитаемости или плохой производительности, а в предположениях и гарантиях относительно графика потока управления. Любое злоупотребление goto было бы для повышения производительности (или читабельности).
Андерс Эурениус
3
Я бы поспорил одна из причин gotoявляется полезным является то , что она позволяет построить петлю , как это, что потребовало бы кутит логические судороги иначе. Кроме того, я бы сказал, что если оптимизатор не знает, как это переписать, то это хорошо . Подобный цикл должен быть сделан не для производительности или читабельности, а потому, что это именно тот порядок, в котором все должно происходить. В этом случае я бы не хотел, чтобы оптимизатор работал с ним.
Цао
1
... прямое преобразование требуемого алгоритма в код на основе GOTO может быть гораздо проще проверить, чем путаница флаговых переменных и неправильных циклических конструкций.
суперкат
7

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

Рикардо
источник
1
В приведенном вами примере я предпочел бы провести довольно значительный рефакторинг. В общем, я стараюсь избегать однострочных комментариев, в которых говорится о том, что делает следующий кусок кода. Вместо этого я делю это на свою собственную функцию, которая называется похожей на комментарий. Если бы вы сделали такое преобразование, то эта функция дала бы общий обзор того, что делает функция, и каждая из новых функций будет указывать, как вы выполняете каждый шаг. Я чувствую, что гораздо важнее, чем любая оппозиция, gotoиметь каждую функцию на одном уровне абстракции. То, что он избегает, gotoявляется бонусом.
Дэвид Стоун
2
Вы действительно не объяснили, как добавление большего количества функций избавляет от перехода, множественных уровней отступов и ложных операторов if ...
Рикардо,
Это выглядело бы примерно так, используя стандартные обозначения контейнеров: container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();я считаю, что такого рода программирование намного легче читать. Каждая функция в этом случае может быть написана как чистая функция, о которой гораздо проще рассуждать. Функция main теперь объясняет, что делает код, просто по имени функций, а затем, если хотите, вы можете посмотреть на их определения, чтобы узнать, как он это делает.
Дэвид Стоун
3
Тот факт, что каждый должен приводить примеры того, как определенные алгоритмы должны естественным образом использовать goto, - печальное отражение того, как мало алгоритмического мышления происходит сегодня !! Конечно, пример @ Ricardo (один из многих) является идеальным примером того, где goto элегантно и очевидно.
Толстяк
6

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

Ари Перник
источник
1
Я мог видеть что-то подобное в таком языке, как C. Однако, когда вы обладаете мощью конструкторов / деструкторов C ++, это обычно не так полезно.
Дэвид Стоун
1
«В действительно сложных функциях мы ослабляем это правило, чтобы позволить другим переходить вперед». Без примера это звучит так, если вы делаете сложные функции еще более сложными, используя переходы. Не будет ли "лучшим" подходом рефакторинг и разделение этих сложных функций?
MikeMB
1
Это была последняя цель, для которой я использовал goto. Я не пропускаю goto в Java, потому что в нем есть try-finally, которое может выполнять ту же работу.
Патриция Шанахан
5

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

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

nhcohen
источник
3

В модуле 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
  ...
}
Брэд Гилберт
источник
2

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

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

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

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

DrPizza
источник
2

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

Кен Рэй
источник
8
Или в C ++, C #, Java, JS, Python, Ruby .... и т. Д. И т. Д. И т. Д. Только они называют это «исключениями».
cHao
2

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

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

for (stepfailed=0 ; ! stepfailed ; /*empty*/)
Сэнди
источник
Не должно ли это /*empty*/быть stepfailed = 1? В любом случае, как это лучше, чем do{}while(0)? В обоих случаях вам нужно breakиз нее (или в свою stepfailed = 1; continue;). Кажется ненужным для меня
Томас Эдинг
2

1) Наиболее распространенное использование goto, о котором я знаю, - это эмуляция обработки исключений в языках, которые этого не предлагают, а именно в C. (Код, приведенный выше в Nuclear, просто так.) Посмотрите на исходный код Linux, и вы ' увидим, как таким образом использовался базилик гото; согласно короткому опросу, проведенному в 2013 году, в коде Linux было около 100 000 кодов goto: 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 - переменная. Весьма странно, что в Фортране «вычисляемое 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, если они только передовые.

шипение
источник
Правильно. «Слон в комнате» заключается в том, что ядро ​​Linux, ради бога, как один из примеров всемирно важной базы кода - загружено с помощью goto. Конечно, это является. Очевидно. «Анти-гото-мем» - это просто любопытство десятилетий назад. Конечно, в программировании есть ряд вещей (в частности, «статика», на самом деле «глобал» и, например, «elseif»), которыми могут злоупотреблять непрофессионалы. Итак, если вы кузен, ребенок учится в программе 2, вы говорите им: «Не используйте глобальные переменные» и «Никогда не используйте elseif».
Толстяк
Ошибка goto fail не имеет ничего общего с goto. Проблема вызвана оператором if без скобок впоследствии. Почти любая копия заявления, вставленная дважды, создала бы проблему. Я считаю, что этот тип обнаженных людей безвреден, если он гораздо более вреден, чем goto.
muusbolla
2

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

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

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

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

Abhinav
источник
Я думаю, что единственная форма goto, которую вы когда-либо использовали в Perl - это goto &subroutine. Который запускает подпрограмму с текущим @_, заменяя текущую подпрограмму в стеке.
Брэд Гилберт
1

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

cschol
источник
11
Выражение «я не могу думать ни о какой причине» в вашем аргументе формально звучит как en.wikipedia.org/wiki/Argument_from_ignorance , хотя я предпочитаю терминологию «доказательство недостатком воображения».
ТОЛЬКО МОЕ правильное мнение
2
@ ТОЛЬКО МОЕ правильное МНЕНИЕ: Это логическая ошибка, только если она используется постфактум. С точки зрения разработчика языка, это может быть веским аргументом для оценки стоимости включения функции ( goto). Использование @ cschol аналогично: хотя, возможно, сейчас он не разрабатывает язык, он (а) в основном оценивает усилия дизайнера.
Конрад Рудольф
1
@KonradRudolph: ИМХО, наличие языкового разрешения, gotoза исключением случаев, когда оно приводит к появлению переменных, может быть дешевле, чем пытаться поддерживать все виды управляющих структур, которые могут кому-то понадобиться. Написание кода с использованием gotoможет быть не таким приятным, как использование какой-либо другой структуры, но возможность написания такого кода gotoпоможет избежать «дыр в выразительности» - конструкций, для которых язык не способен писать эффективный код.
суперкат
1
@supercat Боюсь, мы из разных школ языкового дизайна. Я возражаю против того, чтобы языки были максимально выразительными за счет понятности (или правильности). Я предпочел бы иметь ограничительный, чем разрешительный язык.
Конрад Рудольф
1
@ thb Да, конечно. Это часто делает код намного сложнее для чтения и рассуждений. Практически каждый раз, когда кто-то публикует код, содержащийся gotoна сайте обзора кода, устранение этого gotoзначительно упрощает логику кода.
Конрад Рудольф
1

Конечно, 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
источник
6
Это часто имело место в старой школе BASIC. Однако в современных вариантах вам не разрешено переходить в середину другой функции ... или во многих случаях даже после объявления переменной. В основном (каламбур не предназначен), современные языки в значительной степени покончили с «необузданными» GOTO, о которых говорил Дейкстра ... и единственный способ использовать это, как он спорил, - это совершить некоторые другие отвратительные грехи. :)
cHao
1

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

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

ядерной
источник
3
Вот почему у нас есть finallyблоки в C #
Джон Сондерс
^ как сказал @JohnSaunders. Это пример «использования goto, потому что в языке отсутствует соответствующая управляющая конструкция». ОДНАКО это кодовый запах, требующий НЕСКОЛЬКО точек перехода рядом с возвратом. Существует стиль программирования, который более безопасен (легче не испортить ) И не требует gotos, который прекрасно работает даже в языках без «finally»: проектируйте эти вызовы «очистки» так, чтобы они были безвредны, когда не очищаются вверх требуется. Фактор все, кроме очистки в метод, который использует дизайн множественного возврата. Вызовите этот метод, затем выполните очистительные вызовы.
ToolmakerSteve
Обратите внимание, что подход, который я описываю, требует дополнительного уровня вызова метода (но только на языке, в котором отсутствует finally). В качестве альтернативы используйте gotos, но для общей точки выхода, которая всегда выполняет всю очистку. Но каждый метод очистки может обрабатывать нулевое или уже очищенное значение или защищен условным тестом, поэтому пропускается, когда не подходит.
ToolmakerSteve
@ToolmakerSteve это не запах кода; на самом деле, это очень распространенный паттерн в C и считается одним из наиболее эффективных способов использования goto. Вы хотите, чтобы я создал 5 методов, каждый со своим ненужным if-test, просто для очистки от этой функции? Вы теперь создали код и производительность вздулись. Или вы можете просто использовать goto.
muusbolla
@muusbolla - 1) "создать 5 методов" - Нет. Я предлагаю один метод, который очищает любые ресурсы, которые имеют ненулевое значение. ИЛИ посмотрите мою альтернативу, которая использует gotos, которые все идут к одной и той же точке выхода, которая имеет ту же логику (которая требует, как вы говорите, дополнительного if для ресурса). Но не берите в голову, что при использовании Cвы правы - независимо от того, по какой причине код написан на C, это почти наверняка компромисс, который благоприятствует наиболее «прямому» коду. (Мое предложение ручки сложных ситуаций , в которых могут быть или не были выделены любой данный ресурс Но да, излишеством в этом случае.)
ToolmakerSteve
0

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

bubbassauro
источник
0

Время от времени он пригодится для посимвольной обработки строк.

Представьте себе что-то вроде этого printf-esque:

for cur_char, next_char in sliding_window(input_string) {
    if cur_char == '%' {
        if next_char == '%' {
            cur_char_index += 1
            goto handle_literal
        }
        # Some additional logic
        if chars_should_be_handled_literally() {
            goto handle_literal
        }
        # Handle the format
    }
    # some other control characters
    else {
      handle_literal:
        # Complicated logic here
        # Maybe it's writing to an array for some OpenGL calls later or something,
        # all while modifying a bunch of local variables declared outside the loop
    }
}

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

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

Beefster
источник