Я понимаю, что за исключением разрыва циклов, вложенных в циклы; это goto
утверждение уклоняется и осуждается как склонный к ошибкам стиль программирования, который никогда не будет использоваться.
Alt Text: «Нил Стивенсон считает, что называть его ярлыки« деньгами »мило».
См. Оригинальный комикс по адресу: http://xkcd.com/292/
Потому что я узнал это рано; У меня нет никакого понимания или опыта, к каким типам ошибок goto
это приводит. Итак, о чем мы говорим здесь:
- Нестабильность?
- Неработоспособный или нечитаемый код?
- Уязвимости безопасности?
- Что-то еще целиком?
К каким ошибкам в действительности приводят утверждения "goto"? Есть ли исторически значимые примеры?
Ответы:
Вот способ посмотреть на это, я еще не видел в других ответах.
Речь идет о сфере. Один из главных столпов хорошей практики программирования - держать ваш прицел в узком месте. Вам нужен узкий кругозор, потому что у вас нет умственных способностей контролировать и понимать больше, чем просто несколько логических шагов. Таким образом, вы создаете маленькие блоки (каждый из которых становится «одной вещью»), а затем строите их, чтобы создать большие блоки, которые становятся одной вещью, и так далее. Это делает вещи управляемыми и понятными.
Goto эффективно раздувает объем вашей логики для всей программы. Это наверняка победит ваш мозг для любых, кроме самых маленьких программ, занимающих всего несколько строк.
Таким образом, вы не сможете определить, сделали ли вы ошибку больше, потому что слишком много нужно взять и проверить свою бедную маленькую голову. Это реальная проблема, ошибки - только вероятный результат.
источник
Не то чтобы
goto
это плохо само по себе. (В конце концов, каждая инструкция перехода в компьютере - это goto.) Проблема в том, что существует человеческий стиль программирования, предшествующий структурированному программированию, который можно назвать программированием «блок-схемы».При программировании блок-схем (которые изучали люди моего поколения и которые использовали для программы Apollo moon) вы создаете диаграмму с блоками для выполнения операторов и бриллиантами для принятия решений, и вы можете соединить их линиями, которые идут повсюду. (Так называемый «код спагетти».)
Проблема с кодом спагетти в том, что вы, программист, могли «знать», что это правильно, но как вы можете доказать это себе или кому-либо еще? На самом деле, это может иметь потенциальное неправильное поведение, и ваше знание того, что оно всегда правильно, может быть ошибочным.
Наряду с этим появилось структурированное программирование с начальными и конечными блоками, пока, если-еще и так далее. У них было преимущество в том, что вы все равно могли делать с ними что угодно, но если вы были осторожны, вы могли быть уверены, что ваш код был верным.
Конечно, люди все еще могут писать спагетти-код даже без него
goto
. Распространенным методом является запись awhile(...) switch( iState ){...
, где в разных случаях устанавливаютсяiState
разные значения. На самом деле, в C или C ++ можно написать макрос, чтобы сделать это, и назвать егоGOTO
, так что сказать, что вы не используете,goto
- это различие без разницы.В качестве примера того, как проверка кода может предотвратить неограниченное
goto
, я давным-давно наткнулся на структуру управления, полезную для динамически изменяющихся пользовательских интерфейсов. Я назвал это дифференциальным исполнением . Это полностью Тьюринга универсальное, но его доказательство корректности зависит от чисто структурного программирования - нетgoto
,return
,continue
,break
, или исключение.источник
Почему Goto опасно?
goto
не вызывает нестабильности сам по себе. Несмотря на примерно 100 000goto
с, ядро Linux остается моделью стабильности.goto
само по себе не должно вызывать уязвимостей в безопасности. Однако в некоторых языках смешивание его с блоками управленияtry
/catch
исключений может привести к уязвимости, как объяснено в этой рекомендации CERT . Основные компиляторы C ++ помечают и предотвращают такие ошибки, но, к сожалению, старые или более экзотические компиляторы этого не делают.goto
вызывает нечитаемый и не поддерживаемый код. Это также называется кодом спагетти , потому что, как и в тарелке спагетти, очень трудно следить за потоком контроля, когда слишком много gotos.Даже если вам удастся избежать спагетти-кода и если вы используете только несколько gotos, они все равно способствуют таким ошибкам, как утечка ресурсов:
goto
заявления вы нарушаете этот простой поток и разбиваете ожидания. Например, вы можете не заметить, что вам еще нужно освободить ресурсы.goto
в разных местах могут отправить вас к одной цели goto. Так что не очевидно, чтобы точно знать, в каком состоянии вы находитесь, достигнув этого места. Следовательно, риск ошибочных / необоснованных предположений достаточно велик.Дополнительная информация и цитаты:
Э.Дейкстра написал раннее эссе на эту тему уже в 1968 году: « Перейти к заявлению, которое считается вредным »
Brian.W.Kernighan & Dennis.M.Ritchie написал на языке программирования C:
Джеймс Гослинг и Генри МакГилтон написали в своем техническом документе по языковой среде Java 1995 года :
Бьярн Страуструп определяет Гото в своем глоссарии в таких привлекательных терминах:
Когда можно пойти?
Как и K & R, я не догматичен по поводу gotos. Я признаю, что бывают ситуации, когда goto может облегчить жизнь.
Как правило, в C goto разрешает многоуровневый выход из цикла или обработку ошибок, требующую достижения соответствующей точки выхода, которая освобождает / разблокирует все ресурсы, которые были выделены до сих пор (т.е. многократное распределение в последовательности означает несколько меток). Эта статья дает количественную оценку различных вариантов использования goto в ядре Linux.
Лично я предпочитаю избегать этого, и через 10 лет я использовал максимум 10 gotos. Я предпочитаю использовать вложенные
if
s, которые я считаю более читабельными. Когда это приведет к слишком глубокому вложению, я решу либо разложить свою функцию на более мелкие части, либо использовать логический индикатор в каскаде. Современные оптимизирующие компиляторы достаточно умны, чтобы генерировать практически тот же код, что и тот же код, с которымgoto
.Использование goto сильно зависит от языка:
В C ++ правильное использование RAII заставляет компилятор автоматически уничтожать объекты, которые выходят за пределы области видимости, так что ресурсы / блокировки все равно будут очищены, и больше нет необходимости в goto.
В Java нет необходимости Гото (см автора цитаты в Java выше , и этот превосходный Stack Overflow ответ ): сборщик мусора , который очищает беспорядок,
break
,continue
иtry
/catch
обработка исключений охватывает все тот случай , когдаgoto
может быть полезными, но в более безопасном и лучше манера. Популярность Java доказывает, что на современном языке можно избежать утверждения goto.Увеличьте известную уязвимость SSL goto fail
Важный отказ от ответственности: ввиду жесткой дискуссии в комментариях я хочу пояснить, что я не претендую на то, что утверждение goto является единственной причиной этой ошибки. Я не делаю вид, что без goto не было бы ошибки. Я просто хочу показать, что goto может быть связано с серьезной ошибкой.
Я не знаю, сколько серьезных ошибок связано с
goto
историей программирования: детали часто не сообщаются. Однако была известная ошибка Apple SSL, которая ослабила безопасность iOS. Утверждение, которое привело к этой ошибке, было невернымgoto
.Некоторые утверждают, что основной причиной ошибки был не сам оператор goto, а неправильное копирование / вставка, вводящее в заблуждение отступ, отсутствие фигурных скобок вокруг условного блока или, возможно, рабочие привычки разработчика. Я не могу ни подтвердить ни один из них: все эти аргументы являются вероятными гипотезами и интерпретациями. Никто на самом деле не знает. ( между тем, гипотеза о слиянии, которая пошла не так, как кто-то предложил в комментариях, кажется очень хорошим кандидатом ввиду некоторых других несоответствий отступов в той же функции ).
Единственный объективный факт заключается в том, что дублирование
goto
привело к преждевременному выходу из функции. Если посмотреть на код, единственным другим оператором, который мог бы вызвать такой же эффект, был бы возврат.Ошибка в функции
SSLEncodeSignedServerKeyExchange()
в этом файле :Действительно, фигурные скобки вокруг условного блока могли бы предотвратить ошибку:
это привело бы либо к синтаксической ошибке при компиляции (и, следовательно, к исправлению), либо к избыточному безопасному переходу. Кстати, GCC 6 сможет обнаружить эти ошибки благодаря своему необязательному предупреждению для обнаружения несовместимых отступов.
Но, во-первых, всех этих проблем можно было бы избежать с помощью более структурированного кода. Таким образом, goto является косвенной причиной этой ошибки. Есть по крайней мере два разных способа, которые могли бы избежать этого:
Подход 1: если оговорка или вложенная
if
sВместо того, чтобы последовательно проверять множество условий на наличие ошибок и каждый раз отправлять
fail
метку в случае возникновения проблемы, можно было быif
выбрать выполнение криптографических операций в -состоянии, которое делало бы это, только если не было неправильного предварительного условия:Подход 2: использовать аккумулятор ошибок
Этот подход основан на том факте, что почти все приведенные здесь операторы вызывают некоторую функцию для установки
err
кода ошибки и выполняют остальную часть кода только в том случае, еслиerr
было 0 (то есть функция выполнялась без ошибок). Хорошая безопасная и читаемая альтернатива:Здесь нет ни одного перехода: нет риска быстро перейти к точке выхода из строя. И визуально было бы легко обнаружить смещенную линию или забытую
ok &&
.Эта конструкция более компактна. Он основан на том факте, что в C вторая часть логического и (
&&
) оценивается, только если первая часть истинна. Фактически, ассемблер, сгенерированный оптимизирующим компилятором, почти эквивалентен исходному коду с gotos: оптимизатор очень хорошо обнаруживает цепочку условий и генерирует код, который при первом ненулевом возвращаемом значении переходит к концу ( онлайн-подтверждение ).Вы могли бы даже предусмотреть проверку согласованности в конце функции, которая могла бы на этапе тестирования выявлять несоответствия между флагом ok и кодом ошибки.
Ошибки, такие как
==0
непреднамеренная замена!=0
логическим или ошибочным соединителем, легко обнаружатся на этапе отладки.Как сказано: я не претендую на то, что альтернативные конструкции позволили бы избежать любой ошибки. Я просто хочу сказать, что они могли затруднить возникновение ошибки.
источник
goto
внутри оператора без скобокif
. Но если вы предполагаете, что избеганиеgoto
сделает ваш код невосприимчивым к эффекту случайно дублированных строк, у меня для вас плохие новости.if
утверждение? Веселые времена. :)Знаменитая статья Dijkstra была написана в то время, когда некоторые языки программирования были способны создавать подпрограммы, имеющие несколько точек входа и выхода. Другими словами, вы можете буквально перейти в середину функции и выпрыгнуть в любом месте внутри функции, фактически не вызывая эту функцию или не возвращаясь из нее обычным способом. Это все еще верно для ассемблера. Никто никогда не утверждает, что такой подход превосходит структурированный метод написания программного обеспечения, который мы сейчас используем.
В большинстве современных языков программирования функции очень точно определены с одной точкой входа и одной точкой выхода. В точке входа вы указываете параметры функции и вызываете ее, а в точке выхода вы возвращаете полученное значение и продолжаете выполнение по инструкции, следующей за исходным вызовом функции.
В рамках этой функции вы должны иметь возможность делать все, что пожелаете, в пределах разумного. Если использование функции goto или two делает ее более понятной или повышает вашу скорость, почему бы и нет? Смысл функции в том, чтобы отделить немного четко определенной функциональности, чтобы вам больше не приходилось думать о том, как она работает внутри. Как только это написано, вы просто используете его.
И да, вы можете иметь несколько операторов return внутри функции; всегда есть одно место в правильной функции, из которой вы возвращаетесь (в основном, обратная сторона функции). Это совсем не то же самое, что выпрыгнуть из функции с помощью goto до того, как у нее появится шанс вернуться.
Так что дело не в использовании goto. Речь идет о том, чтобы избежать их злоупотребления. Все согласны с тем, что вы можете создать ужасно запутанную программу, используя gotos, но вы можете сделать то же самое, злоупотребляя функциями (гораздо проще злоупотреблять gotos).
Что бы это ни стоило, с тех пор, как я закончил программы BASIC в стиле «номер строки» к структурированному программированию с использованием языков Pascal и фигурных скобок, мне никогда не приходилось использовать goto. Единственный раз, когда я испытал желание использовать один из них, это сделать ранний выход из вложенного цикла (в языках, которые не поддерживают многоуровневый ранний выход из циклов), но обычно я могу найти другой способ, который будет чище.
источник
Раньше я использовал
goto
операторы при написании программ BASIC в детстве, как простой способ получить циклы for и while (в Commodore 64 BASIC не было циклов while, и я был слишком незрелым, чтобы изучать правильный синтаксис и использование циклов for. ). Мой код был часто тривиальным, но любые ошибки цикла могли быть немедленно приписаны моему использованию goto.Сейчас я использую в первую очередь Python, язык программирования высокого уровня, который определил, что нет необходимости в goto.
Когда в 1968 году Эдсгер Дейкстра объявил «Goto признан вредным», он не привел несколько примеров, в которых можно было бы обвинить связанные ошибки в Goto, скорее, он заявил, что это
goto
не нужно для языков более высокого уровня и что его следует избегать в пользу того, что сегодня мы рассматриваем нормальный поток управления: циклы и условные выражения. Его слова:Вероятно, у него было множество примеров ошибок, возникающих при каждом отладке кода
goto
. Но его статья была обобщенным заявлением о позиции, подкрепленным доказательствами, которыеgoto
не нужны для языков более высокого уровня. Обобщенная ошибка заключается в том, что у вас может не быть возможности статически анализировать рассматриваемый код.Код гораздо сложнее статически анализировать с помощью
goto
операторов, особенно если вы вернетесь назад в свой поток управления (что я делал раньше) или в какой-то не связанный раздел кода. Код может и был написан таким образом. Это было сделано для высокой оптимизации очень ограниченных вычислительных ресурсов и, следовательно, архитектуры машин.Апокрифический пример
Была программа для блэкджека, которую разработчик посчитал довольно элегантно оптимизированной, но которую он «исправить» не мог из-за природы кода. Он был запрограммирован в машинном коде, который сильно зависит от gotos - поэтому я думаю, что эта история весьма актуальна. Это лучший канонический пример, который я могу придумать.
Контрпример
Тем не менее, исходный код CPython для C (наиболее распространенная и ссылочная реализация Python) использует
goto
операторы с большим эффектом. Они используются для обхода нерелевантного потока управления внутри функций для достижения конца функций, что делает функции более эффективными без потери читабельности. Это учитывает один из идеалов структурного программирования - иметь единую точку выхода для функций.Для справки, я считаю, что этот контрпример довольно легко анализировать статически.
источник
Когда вигвам был современным архитектурным искусством, их строители, несомненно, могли дать вам здравый практический совет относительно конструкции вигвамов, как позволить дыму уйти и так далее. К счастью, сегодняшние строители, вероятно, могут забыть большинство этих советов.
Когда дилижанс был современным транспортным искусством, его водители, несомненно, могли дать вам хороший практический совет относительно лошадей дилижансов, как защитить от разбойников и так далее. К счастью, сегодняшние автомобилисты тоже могут забыть большинство этих советов.
Когда перфокарты были современным искусством программирования, их практикующие также могли бы дать вам здравый практический совет относительно организации карточек, порядка нумерации выписок и так далее. Я не уверен, что этот совет очень актуален сегодня.
Вы достаточно взрослые, чтобы знать фразу «для нумерации» ? Вам не нужно это знать, но если вы не знаете, то вы не знакомы с историческим контекстом, в котором предупреждение
goto
было принципиально актуальным.Предупреждение против
goto
просто не очень актуально сегодня. С базовым обучением циклам while / for и вызовам функций вы даже не будете думать о выпускеgoto
очень часто. Когда вы думаете об этом, у вас, вероятно, есть причина, так что продолжайте.Но можно ли этим
goto
утверждением не злоупотреблять?Ответ: конечно, им можно злоупотреблять, но его злоупотребление - довольно крошечная проблема в разработке программного обеспечения по сравнению с гораздо более распространенными ошибками, такими как использование переменной, в которой будет использоваться константа, или, например, программирование методом вырезания и вставки (в противном случае известный как пренебрежение к рефакторингу). Я сомневаюсь, что вы в большой опасности. Если вы не используете
longjmp
или иным образом не переносите управление в удаленный код, если вы думаете, что хотите использовать егоgoto
или просто хотите попробовать его, просто продолжайте. Вам будет хорошо.Вы можете заметить отсутствие недавних ужасов, в которых
goto
играет злодея. Большинству или всем из этих историй кажется 30 или 40 лет. Вы стоите на твердой почве, если считаете, что эти истории в основном устарели.источник
Чтобы добавить одно к другим отличным ответам, с помощью
goto
утверждений может быть трудно точно сказать, как вы попали в какое-то конкретное место в программе. Вы можете знать, что исключение произошло в какой-то конкретной строке, но еслиgoto
в коде присутствуют исключения, невозможно определить, какие операторы выполняются, чтобы привести к состоянию, вызывающему это исключение, без поиска всей программы. Нет стека вызовов и нет визуального потока. Возможно, что на расстоянии 1000 строк есть оператор, который поставил вас в плохое состояние, выполнивgoto
строку, вызвавшую исключение.источник
On Error Goto errh
, вы можете использовать,Resume
чтобы повторить попытку,Resume Next
пропустить ошибочную строку иErl
узнать, какая это была строка (если вы используете номера строк).Resume
выполнит эту функцию с самого начала иResume Next
пропустит все функции, которые еще не были выполнены.On Error Goto 0
и отладки вручную.Resume
предназначен для использования после проверкиErr.Number
и внесения необходимых корректировок.goto
работает только с помеченными линиями, которые, как представляется, были заменены помеченными циклами .goto людям сложнее рассуждать, чем другим формам контроля потока.
Программировать правильный код сложно. Трудно писать правильные программы, определить, верны ли программы, сложно, а правильно проверить программы - сложно.
Получить код, который смутно делает то, что вы хотите, легко по сравнению со всем остальным в программировании
Goto решает некоторые программы с получением кода, чтобы делать то, что вы хотите. Это не помогает облегчить проверку правильности, в то время как ее альтернативы часто делают.
Существуют стили программирования, где goto является подходящим решением. Есть даже стили программирования, где его злой близнец, оттуда, является подходящим решением. В обоих этих случаях необходимо проявлять крайнюю осторожность, чтобы убедиться, что вы используете его в понятном порядке, и много ручной проверки того, что вы не делаете что-то сложное, чтобы убедиться в его правильности.
Например, в некоторых языках есть функция сопрограмм. Сопрограммы - нити без ниток; состояние выполнения без потока для его запуска. Вы можете попросить их выполнить, и они могут запустить часть себя, а затем приостановить, передав контроль потока обратно.
«Неглубокие» сопрограммы в языках без поддержки сопрограмм (например, C ++ pre-C ++ 20 и C) возможны с использованием сочетания gotos и ручного управления состоянием. «Глубокие» сопрограммы могут быть выполнены с использованием функций
setjmp
иlongjmp
C.Есть случаи, когда сопрограммы настолько полезны, что написание их вручную и тщательно стоит того.
В случае C ++ они оказываются достаточно полезными, чтобы расширить язык для их поддержки. В
goto
s и ручное управление состоянием, скрытого уровня абстракции нулевой стоимости, что позволяет программистам писать их без трудностью того , чтобы доказать свою путаницу Гото,void**
с, ручное построение / разрушение государства и т.д. правильно.Goto скрывается за абстракцией более высокого уровня, вроде
while
илиfor
илиif
или илиswitch
. Эти абстракции более высокого уровня легче доказать и проверить.Если в языке не хватает некоторых из них (например, в некоторых современных языках отсутствуют сопрограммы), то ваши альтернативы могут быть либо в сочетании с шаблоном, который не подходит к проблеме, либо с помощью goto.
Заставить компьютер смутно делать то, что вы хотите, в обычных случаях легко . Написание надежно надежного кода сложно . Гото помогает первому намного больше, чем второму; следовательно, «goto считается вредным», так как это признак поверхностно «работающего» кода с глубокими и трудными для отслеживания ошибок. При достаточных усилиях вы все равно можете сделать код с «goto» надежно надежным, поэтому придерживаться правила абсолютного неверно; но, как правило, это хорошо.
источник
longjmp
допустимо только в C для разматывания стека обратноsetjmp
в родительскую функцию. (Как разрыв нескольких уровней вложенности, но для вызовов функций вместо циклов).longjjmp
к контексту из функцииsetjmp
done в функции, которая с тех пор вернулась, не разрешено (и я подозреваю, что в большинстве случаев это не сработает). Я не знаком с сопрограммами, но из вашего описания подпрограммы, похожей на поток в пользовательском пространстве, я думаю, что для этого потребуется собственный стек, а также контекст сохраненного регистра, и онlongjmp
дает только регистр. -сохранение, а не отдельный стек.Взгляните на этот код из http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html, который на самом деле был частью симуляции большой дилеммы заключенного. Если вы видели старый код FORTRAN или BASIC, вы поймете, что это не так уж необычно.
Здесь много вопросов, которые выходят далеко за рамки заявления GOTO; Я искренне думаю, что заявление GOTO было чем-то вроде козла отпущения. Но поток управления здесь абсолютно неясен, и код смешивается таким образом, что очень неясно, что происходит. Даже без добавления комментариев или использования более подходящих имен переменных, если изменить это на блочную структуру без GOTO, будет намного легче читать и следовать.
источник
Одним из принципов поддерживаемого программирования является инкапсуляция . Дело в том, что вы взаимодействуете с модулем / подпрограммой / подпрограммой / компонентом / объектом, используя определенный интерфейс и только этот интерфейс, и результаты будут предсказуемы (при условии, что устройство было эффективно протестировано).
В пределах одной единицы кода применяется тот же принцип. Если вы последовательно применяете принципы структурного или объектно-ориентированного программирования, вы не будете:
Некоторые из наиболее распространенных симптомов этих ошибок обработки включают утечки памяти, накопление памяти, переполнение указателей, сбои, неполные записи данных, исключения добавления / chg / delete записи, сбои страниц памяти и так далее.
Наблюдаемые пользователем проявления этих проблем включают блокировку пользовательского интерфейса, постепенное снижение производительности, неполные записи данных, невозможность инициировать или завершать транзакции, поврежденные данные, перебои в сети, перебои в подаче электроэнергии, сбои встроенных систем (из-за потери контроля над ракетами). к потере способности к отслеживанию и управлению в системах управления воздушным движением), сбоям таймера и т. д. и т. д. Каталог очень обширный.
источник
goto
ни разу. :)goto опасно, потому что обычно используется там, где не нужно. Используя все , что не требуется опасно, но Гото особенно. Если вы пользуетесь Google, вы обнаружите множество ошибок, вызванных goto, это само по себе не является причиной его отказа (ошибки всегда возникают, когда вы используете языковые функции, потому что это присуще программированию), но некоторые из них явно тесно связаны для Гото использования.
Причины использовать / не использовать goto :
Если вам нужен цикл, вы должны использовать
while
илиfor
.Если вам нужно сделать условный переход, используйте
if/then/else
Если вам нужна процедура, вызовите функцию / метод.
Если вам нужно выйти из функции, просто
return
.Я могу посчитать по пальцам места, где я видел,
goto
использовал и использовал правильно.В libKTX есть функция, которая имеет следующий код
Теперь в этом месте
goto
полезно, потому что язык C:Этот вариант использования полезен, потому что в C у нас нет классов, поэтому самый простой способ очистить это использование a
goto
.Если бы у нас был тот же код на C ++, больше нет необходимости в goto:
К каким ошибкам это может привести? Что-нибудь. Бесконечные циклы, неправильный порядок выполнения, стопорный винт.
Документированный случай - это уязвимость в SSL, которая позволила атакам «Человек посередине», вызванным неправильным использованием goto: вот статья
Это опечатка, но какое-то время она оставалась незамеченной, если бы код был структурирован иным образом, протестирован должным образом, такая ошибка не могла быть возможной.
источник