GOTO все еще считается вредным?

Все знают о письмах Дейкстры редактору: переходите к утверждению, которое считается вредным (также здесь.html transcripts и здесь.pdf), и с того времени был огромный толчок, чтобы по возможности отказаться от оператора goto. Хотя можно использовать goto для создания неуправляемого, растягивающегося кода, тем не менее, он остается в современных языках программирования. Даже усовершенствованная структура управления продолжением в Схеме может быть описана как сложный переход.

Какие обстоятельства оправдывают использование goto? Когда лучше избегать?

В качестве дополнительного вопроса: C предоставляет пару функций setjmp и longjmp, которые обеспечивают возможность перехода не только в текущем кадре стека, но и в любом из вызывающих кадров. Должны ли они считаться такими же опасными, как goto? Более опасный?


Сам Дейкстра пожалел о том звании, за которое не нес ответственности. В конце EWD1308 (также здесь.pdf) он написал:

Наконец короткая история для записи. В 1968 году в сообщениях АСМ был опубликован мой текст под заголовком " Заявление Гото считается вредным ", на который в последующие годы чаще всего будут ссылаться, к сожалению, однако, часто авторы, которые видели его не больше, чем его title, который стал краеугольным камнем моей славы, став шаблоном: мы увидели бы все виды статей под заголовком "X считается вредным" практически для любого X, включая статью под названием "Дейкстра считается вредным". Но что случилось? Я представил документ под названием " Дело против заявления goto ", в котором для ускорения публикации редактор превратился в "письмо в редакцию", и в процессе он дал ему новое титул собственного изобретения! Редактором был Никлаус Вирт.

Дональд Э. Кнут предлагает хорошо продуманную классическую статью на эту тему, которую можно сравнить с темой Дейкстры, - " Структурированное программирование с переходом к утверждениям ". Чтение обоих помогает восстановить контекст и недогматическое понимание предмета. В этой статье сообщается мнение Дейкстры по этому делу, и оно еще более убедительно:

Дональд Э. Кнут: Я полагаю, что, представляя такую ​​точку зрения, я на самом деле не сильно не согласен с идеями Дейкстры, поскольку он недавно написал следующее: "Пожалуйста, не попадитесь в ловушку веры в то, что я ужасно догматичен перейти к утверждению]. У меня неприятное ощущение, что другие делают из этого религию, как будто концептуальные проблемы программирования могут быть решены одним трюком, простой формой дисциплины кодирования! "

50 ответов

Решение

Следующие утверждения являются обобщениями; Хотя всегда можно заявить об исключении, обычно (по моему опыту и скромному мнению) риски не стоят.

  1. Неограниченное использование адресов памяти (как GOTO, так и необработанных указателей) предоставляет слишком много возможностей совершать ошибки, которых легко избежать.
  2. Чем больше способов добраться до определенного "места" в коде, тем менее уверенным может быть состояние системы на данный момент. (Увидеть ниже.)
  3. Структурное программирование ИМХО не столько о том, чтобы "избегать GOTO", сколько о том, чтобы структура кода соответствовала структуре данных. Например, повторяющаяся структура данных (например, массив, последовательный файл и т. Д.) Естественным образом обрабатывается повторяющейся единицей кода. Наличие встроенных структур (например, while, for, while, for-each и т. Д.) Позволяет программисту избежать утомительной повторения одних и тех же шаблонов кодированного клише.
  4. Даже если GOTO - это детали реализации низкого уровня (не всегда так!), Это ниже уровня, о котором должен думать программист. Сколько программистов уравновешивают свои личные чековые книжки в двоичном формате? Сколько программистов беспокоятся о том, какой сектор на диске содержит конкретную запись, вместо того, чтобы просто предоставлять ключ для механизма базы данных (и сколько способов может пойти не так, если мы действительно напишем все наши программы в терминах секторов физического диска)?

Сноски к вышесказанному:

Что касается пункта 2, рассмотрим следующий код:

a = b + 1
/* do something with a */

В точке "сделать что-то" в коде мы можем с большой уверенностью заявить, что a больше, чем b, (Да, я игнорирую возможность незаполненного целочисленного переполнения. Давайте не будем искать простой пример.)

С другой стороны, если код читался так:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

Множество способов добраться до ярлыка 10 означает, что нам нужно работать гораздо усерднее, чтобы быть уверенными в отношениях между a а также b в таком случае. (На самом деле, в общем случае это неразрешимо!)

Что касается пункта 4, само понятие "идти куда-то" в коде - всего лишь метафора. Ничто на самом деле не "движется" нигде внутри процессора, кроме электронов и фотонов (для ненужного тепла). Иногда мы отказываемся от метафоры для другой, более полезной. Я помню, что встречал (несколько десятилетий назад!) Язык, на котором

if (some condition) {
  action-1
} else {
  action-2
}

был реализован на виртуальной машине путем компиляции action-1 и action-2 как внеплановых подпрограмм без параметров, а затем с использованием одного кода операции VM с двумя аргументами, который использовал логическое значение условия для вызова одного или другого. Концепция была просто "выбрать, что вызвать сейчас", а не "иди сюда или иди туда". Опять просто смена метафоры.

XKCD's GOTO Comic

Мой коллега сказал, что единственная причина использовать GOTO - это если вы запрограммировали себя так далеко, что это единственный выход. Другими словами, правильный дизайн заблаговременно, и вам не нужно будет использовать GOTO позже.

Я подумал, что этот комикс прекрасно иллюстрирует: "Я мог бы реструктурировать поток программы или использовать вместо этого один маленький" GOTO "". GOTO - это слабый выход, когда у вас слабый дизайн. Велоцирапторы охотятся на слабых.

Иногда допустимо использовать GOTO в качестве альтернативы обработке исключений внутри одной функции:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

Код COM, кажется, попадает в этот шаблон довольно часто.

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

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

Я мог бы просто объявить булеву переменную break и использовать ее как часть условия для каждого цикла, но в этом случае я решил, что GOTO так же практичен и удобен для чтения.

Никакие велоцирапторы не напали на меня.

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

Goto может быть хорош для конечных автоматов. Оператор switch в цикле (в порядке типичной важности): (a) фактически не представляет поток управления, (b) некрасиво, (c) потенциально неэффективен в зависимости от языка и компилятора. Таким образом, вы в конечном итоге пишете одну функцию для каждого состояния и выполняете такие действия, как "return NEXT_STATE;" который даже похож на гото.

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

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

setjmp / longjmp может быть полезен для реализации исключений или поведения, подобного исключению. Несмотря на то, что они не получили всеобщего одобрения, исключения обычно считаются "действительной" структурой контроля.

setjmp / longjmp "более опасны", чем goto, в том смысле, что их сложнее использовать правильно, не говоря уже о понятном.

Никогда не было и не будет никакого языка, на котором было бы наименее сложно написать плохой код. Дональд Кнут.

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

Далее это будут "указатели, считающиеся вредными", затем "печать утки, считающаяся вредной". Тогда кто будет защищать вас, когда они заберут вашу небезопасную программную конструкцию? А?

У нас уже было это обсуждение, и я поддерживаю свою точку зрения.

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

Даже усовершенствованная структура управления продолжением в Схеме может быть описана как сложный переход.

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

Точно так же высказывание "GOTO - это инструмент, и, как и все инструменты, им можно пользоваться и злоупотреблять", совершенно не соответствует действительности. Ни один современный строитель не будет использовать камень и утверждать, что он "инструмент". Камни были заменены молотками. goto был заменен контрольными структурами. Если бы строитель оказался в дикой природе без молотка, он, конечно, вместо этого использовал бы камень. Если программист должен использовать более низкий язык программирования, который не имеет функции X, ну, конечно, она может использовать goto вместо. Но если она использует его где-то еще вместо соответствующей языковой функции, она явно не поняла язык должным образом и использует его неправильно. Это действительно так просто.

В Linux: Использование goto В Kernel Code на Kernel Trap обсуждается с Линусом Торвальдсом и "новым парнем" использование GOTO в коде Linux. Там есть несколько очень хороших моментов, и Линус одет в это обычное высокомерие:)

Некоторые отрывки:

Линус: "Нет, вам промыли мозги люди из CS, которые думали, что Никлаус Вирт действительно знал, о чем говорит. Он не знал. У него нет чертова подсказка".

-

Линус: "Я думаю, что с goto все в порядке, и они часто более читабельны, чем большие отступы".

-

Линус: "Конечно, в глупых языках, таких как Паскаль, где ярлыки не могут быть описательными, goto может быть плохим".

Сегодня трудно понять грандиозное GOTO это утверждение, потому что люди "структурированного программирования" в основном выиграли дебаты, а современные языки имеют достаточную структуру потока управления, чтобы избежать GOTO,

Подсчитать количество gotoс в современной программе C. Теперь добавьте количество break, continue, а также return заявления. Кроме того, добавьте количество раз, которое вы используете if, else, while, switch или же case, Вот примерно сколько GOTOВаша программа была бы, если бы вы писали на фортране или бейсике в 1968 году, когда Дейкстра написал свое письмо.

Языки программирования в то время испытывали недостаток в потоке управления. Например, в оригинальном дартмутском бейсике:

  • IF заявления не имели ELSE, Если вы хотели один, вы должны были написать:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • Даже если ваш IF заявление не нужно ELSE, он по-прежнему ограничивался одной строкой, которая обычно состояла из GOTO,

  • Не было DO...LOOP заявление. Для неFOR циклы, вы должны были закончить цикл с явным GOTO или же IF...GOTO вернуться к началу.

  • Не было SELECT CASE, Вы должны были использовать ON...GOTO,

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

Это где анти-GOTO движение пришло.

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

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

3420 IF A > 2 THEN GOTO 1430

Здесь Линус описывает надлежащее использование goto: http://www.kernel.org/doc/Documentation/CodingStyle (глава 7).

Go To может обеспечить своего рода замену для "реальной" обработки исключений в определенных случаях. Рассматривать:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

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

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

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

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

Дональд Э. Кнут ответил на этот вопрос в книге "Грамотное программирование", 1992 CSLI. На стр. 17 есть эссе " Структурированное программирование с использованием операторов goto" (PDF). Я думаю, что статья могла быть опубликована и в других книгах.

В статье описывается предложение Дейкстры и описываются обстоятельства, в которых оно действует. Но он также приводит ряд контрпримеров (проблем и алгоритмов), которые нельзя легко воспроизвести, используя только структурированные циклы.

Статья содержит полное описание проблемы, историю, примеры и контрпримеры.

Гото считается полезным.

Я начал программировать в 1975 году. Для программистов эпохи 1970-х годов слова "goto считать вредными" говорили более или менее о том, что новые языки программирования с современными структурами управления стоит попробовать. Мы попробовали новые языки. Мы быстро обратились. Мы никогда не возвращались.

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

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

Лозунги, которые никто не понимает, не очень освещают. Вероятно, лучше всего забыть такие лозунги. Такие лозунги не помогают.

Однако именно этот лозунг "Гото считается вредным" обрел собственную жизнь нежити.

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

В отличие от этого, я не могу вспомнить ни одного фактического случая злоупотребления гото с 1990 года.

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

Самое плохое в goto сегодня - это то, что он недостаточно используется.

Привлеченный Джеем Баллоу, добавившим ответ, я добавлю свои £0,02. Если бы Бруно Раншарт еще не сделал этого, я бы упомянул статью Кнута "Структурированное программирование с утверждениями GOTO".

Одна вещь, которую я не видел, обсуждалась, это своего рода код, который, хотя и не совсем распространенный, преподавался в фортранских учебниках. Такие вещи, как расширенный диапазон цикла DO и подпрограммы с открытым кодом (помните, это будет Fortran II, или Fortran IV, или Fortran 66, а не Fortran 77 или 90). По крайней мере, есть вероятность, что синтаксические детали неточны, но концепции должны быть достаточно точными. Фрагменты в каждом случае находятся внутри одной функции.

Обратите внимание, что в превосходной, но устаревшей (и не издаваемой) книге " Элементы стиля программирования, 2nd Edn" Kernighan & Plauger содержатся некоторые реальные примеры злоупотребления GOTO из учебников по программированию его эпохи (конец 70-х годов). Однако материал ниже не из этой книги.

Расширенный диапазон для петли DO

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

Одной из причин такой ерунды была старая старомодная перфокарта. Вы можете заметить, что метки (красиво не по порядку, потому что это был канонический стиль!) Находятся в столбце 1 (фактически, они должны быть в столбцах 1-5), а код - в столбцах 7-72 ​​(столбец 6 был продолжением). маркерная колонка). Столбцам 73-80 будет присвоен порядковый номер, и существуют машины, которые сортируют колоды перфокарт в порядке порядкового номера. Если ваша программа работала с последовательными карточками и вам нужно было добавить несколько карточек (строк) в середину цикла, вам пришлось бы перевыпускать все после этих дополнительных строк. Однако, если вы заменили одну карту на GOTO, вы можете избежать повторного упорядочения всех карт - вы просто подсовываете новые карты в конце процедуры с новыми порядковыми номерами. Считайте, что это первая попытка "зеленых вычислений" - экономия перфокарт (или, более конкретно, экономия при перепечатывании рабочей силы и сохранение ошибок повторного ввода).

О, вы также можете заметить, что я обманываю, а не кричу - Фортран IV был написан в верхнем регистре как обычно.

Открытая подпрограмма

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

GOTO между метками 76 и 54 является версией вычисленного goto. Если переменная i имеет значение 1, перейдите к первой метке в списке (123); если оно имеет значение 2, перейдите ко второму и так далее. Фрагмент от 76 до вычисленного goto является подпрограммой с открытым кодом. Это был кусок кода, выполненный скорее как подпрограмма, но записанный в теле функции. (У Fortran также были операторные функции - встроенные функции, которые помещались в одну строку.)

Существовали худшие конструкции, чем вычисленное goto - вы могли назначать метки переменным, а затем использовать назначенное goto. Googling назначил goto, чтобы сообщить мне, что он был удален из Фортрана 95. Примите участие в революции структурного программирования, которая, можно сказать, началась публично, с письмом или статьей Дейкстры "GOTO считать вредным".

Без некоторого знания того, что было сделано в Фортране (и на других языках, большинство из которых по праву попали на обочину), нам, новичкам, трудно понять масштаб проблемы, с которой сталкивался Дейкстра. Черт, я начал программировать только через десять лет после того, как это письмо было опубликовано (но у меня действительно было несчастье программировать на Фортране IV некоторое время).

Там нет таких вещей, как GOTO считается вредным.

GOTO - это инструмент, и, как и все инструменты, им можно пользоваться и злоупотреблять.

Однако в мире программирования существует множество инструментов, которые склонны злоупотреблять чаще, чем использовать, и GOTO является одним из них. оператор WITH Delphi - это другое.

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

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

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

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

Я написал код, который использовал setjmp / longjmp только один раз. Это было в программе MIDI-барабанного секвенсора. Воспроизведение происходило в отдельном процессе от всего взаимодействия с пользователем, и процесс воспроизведения использовал общую память с процессом пользовательского интерфейса, чтобы получить ограниченную информацию, необходимую для воспроизведения. Когда пользователь хотел остановить воспроизведение, процесс воспроизведения просто выполнял longjmp "назад в начало", чтобы начать сначала, а не какое-то сложное раскручивание того места, где оно выполнялось, когда пользователь хотел остановить его. Это работало отлично, было просто, и у меня никогда не было проблем или ошибок, связанных с этим в этом случае.

У setjmp / longjmp есть свое место - но это место, которое вы вряд ли посетите, но время от времени.

Изменить: я только что посмотрел на код. На самом деле я использовал siglongjmp(), а не longjmp (не то, чтобы это было важно, но я забыл, что siglongjmp даже существует).

Такого никогда не было, пока ты умеешь думать самостоятельно.

Так как goto может быть использован для запутывания метапрограммирования

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

Это низкий уровень в том смысле, что goto - это примитивная операция, которая реализует что-то более высокое, например while или же foreach или что-то.

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

Таким образом, есть прозаическая и злая сторона goto,

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

Злая сторона - это рутина, не использующая goto на время, перерыв или возврат, но использующая ее для так называемой логики спагетти. В этом случае разработчик goto-happy создает куски кода из лабиринта goto, и единственный способ понять это - это симулировать его мысленно в целом, ужасно утомительная задача, когда есть много goto. Я имею в виду, представить себе проблему оценки кода, где else не совсем обратная сторона ifгде вложенный ifs может позволить в некоторых вещах, которые были отвергнуты внешним ifи т. д. и т. д.

Наконец, чтобы действительно охватить эту тему, мы должны отметить, что по существу все ранние языки, кроме Algol, первоначально делали только отдельные утверждения в зависимости от своих версий if-then-else, Таким образом, единственный способ сделать условный блок был goto вокруг него с помощью обратного условного. Безумно, я знаю, но я прочитал некоторые старые спецификации. Помните, что первые компьютеры были запрограммированы в двоичном машинном коде, поэтому я предполагаю, что любой тип HLL был спасителем; Я предполагаю, что они не были слишком разборчивы в том, какие именно функции HLL они получили.

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

Если вы пишете виртуальную машину на C, то получается, что использование (gcc) вычисленных gotos выглядит так:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

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

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

Вот только один небольшой пример кода, который я нашел.

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

-----------------------ИЛИ ЖЕ----------------------

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10

"По этой ссылке http://kerneltrap.org/node/553/2131"

По иронии судьбы, устранение goto привело к ошибке: вызов спин-блокировки был опущен.

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

О единственном месте, где я согласен, можно использовать Goto, когда вам нужно иметь дело с ошибками, и каждый конкретный момент, когда возникает ошибка, требует особой обработки.

Например, если вы захватываете ресурсы и используете семафоры или мьютексы, вы должны захватывать их по порядку и всегда освобождать их противоположным образом.

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

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

-Адам

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

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

Теперь рассмотрим случай очистки, который я сам использовал довольно часто, и он настолько распространен, что предположительно отвечает за структуру try{} catch {}, недоступную во многих языках. Количество проверок и дополнительных переменных, которые требуются для выполнения одного и того же действия, намного хуже, чем одна или две инструкции для перехода, и опять же, решение дополнительной функции вовсе не является решением. Вы не можете сказать мне, что это более управляемо или более читабельно.

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

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

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

У меня был сложный объект, и мне нужно было поработать над ним. Если бы объект находился в одном состоянии, я мог бы сделать быструю версию операции, в противном случае мне пришлось бы делать медленную версию операции. Дело в том, что в некоторых случаях, в середине медленной операции, можно было понять, что это можно было сделать с быстрой операцией.

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

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

Хьюго

Если бы само GOTO было злом, компиляторы были бы злы, потому что они генерировали JMP. Если бы прыжок в блок кода, особенно после указателя, был изначально злым, инструкция RETurn была бы злой. Скорее зло находится в потенциальной возможности злоупотребления.

Временами мне приходилось писать приложения, которые должны были отслеживать количество объектов, где каждый объект должен был следовать сложной последовательности состояний в ответ на события, но все это было определенно однопоточным. Типичная последовательность состояний, если она представлена ​​в псевдокоде:

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

Я уверен, что это не ново, но способ, которым я справился с этим в C(++), заключался в определении некоторых макросов:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

Затем (предполагая, что состояние изначально равно 0), структурированный конечный автомат, приведенный выше, превращается в структурированный код:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

В зависимости от этого, могут быть CALL и RETURN, поэтому некоторые конечные автоматы могут действовать как подпрограммы других конечных автоматов.

Это необычно? Да. Требуется ли некоторое обучение со стороны сопровождающего? Да. Окупается ли это обучение? Я думаю так. Может ли это быть сделано без GOTO, которые прыгают в блоки? Нету.

Одним из современных применений GOTO является компилятор C# для создания конечных автоматов для перечислителей, определяемых возвращаемым доходом.

GOTO - это то, что должно использоваться компиляторами, а не программистами.

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

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

Я лично никогда не использую это явно, никогда не нужно.

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

Это того не стоит.

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

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

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

Другие вопросы по тегам