Goto — это оператор, который встречается во многих языках программирования . Он выполняет одностороннюю передачу управления на другую строку кода; в отличие от этого вызов функции обычно возвращает управление. Места перехода обычно идентифицируются с помощью меток , хотя некоторые языки используют номера строк . На уровне машинного кода a — goto
это форма оператора ветвления или перехода , в некоторых случаях объединенного с корректировкой стека. Многие языки поддерживают этот goto
оператор, а многие — нет (см. § Поддержка языков).
Теорема о структурированной программе доказала, что это goto
утверждение не является необходимым для написания программ, которые можно выразить в виде блок-схем ; некоторая комбинация трех программных конструкций последовательности, выбора/выбора и повторения/итерации достаточна для любого вычисления, которое может быть выполнено машиной Тьюринга , с оговоркой, что может потребоваться дублирование кода и введение дополнительных переменных. [1]
Использование goto ранее было обычным, но с появлением структурного программирования в 1960-х и 1970-х годах его использование значительно сократилось. Он по-прежнему используется в некоторых общих шаблонах использования, но альтернативы обычно используются, если они доступны. В прошлом в академических кругах и промышленности велись серьезные дебаты по поводу достоинств использования операторов goto. Основная критика заключается в том, что код, использующий операторы goto, сложнее для понимания, чем альтернативные конструкции. Дебаты по поводу его (более ограниченного) использования продолжаются в академических кругах и кругах индустрии программного обеспечения.
goto label
Этот goto
оператор часто объединяется с оператором if, чтобы вызвать условную передачу управления.
IF condition THEN goto label
Языки программирования накладывают различные ограничения относительно назначения оператора goto
. Например, язык программирования C не допускает перехода к метке, содержащейся в другой функции, [2] однако переходы внутри одной цепочки вызовов возможны с использованием функций setjmp/longjmp .
На встрече, предшествовавшей ALGOL в 1959 году, Хайнц Земанек явно усомнился в необходимости операторов GOTO; в то время никто [ требуется ссылка ] не обратил внимания на его замечание, включая Эдсгера В. Дейкстру , который позже стал культовым противником GOTO. [3] В 1970-х и 1980-х годах наблюдался спад в использовании операторов GOTO в пользу парадигмы структурного программирования , при этом GOTO критиковали за то, что он приводит к неподдерживаемому спагетти-коду . Некоторые стандарты кодирования стиля программирования , например, GNU Pascal Coding Standards, рекомендуют не использовать операторы GOTO. [4] Доказательство Бёма –Якопини (1966) не решило вопрос о том, следует ли принимать структурное программирование для разработки программного обеспечения, отчасти потому, что конструкция скорее затмевала программу, чем улучшала ее, поскольку ее применение требовало введения дополнительных локальных переменных. [5] Однако это вызвало заметные дебаты среди компьютерных ученых, педагогов, разработчиков языков и прикладных программистов, которые увидели медленный, но устойчивый отход от ранее повсеместного использования GOTO. Вероятно, самая известная критика GOTO - это письмо Эдсгера Дейкстры 1968 года под названием « Go-to statement consider dangerous ». [3] В этом письме Дейкстра утверждал, что неограниченные операторы GOTO должны быть отменены из языков более высокого уровня, поскольку они усложняют задачу анализа и проверки правильности программ (особенно тех, которые включают циклы). [6] Само письмо вызвало дебаты, включая письмо «GOTO Considered Harmful» Considered Harmful [7], отправленное в Communications of the ACM (CACM) в марте 1987 года, а также дальнейшие ответы других людей, включая Dijkstra's On a Somewhat Disappointing Correspondence . [8]
Альтернативная точка зрения представлена в работе Дональда Кнута « Структурное программирование с операторами go to» , в которой анализируются многие общие задачи программирования и делается вывод, что в некоторых из них GOTO является оптимальной языковой конструкцией для использования. [9] В книге «Язык программирования C» Брайан Керниган и Деннис Ритчи предупреждают, что goto
это «бесконечно злоупотребляемо», но также предполагают, что это может быть использовано для обработчиков ошибок конца функции и для многоуровневых разрывов циклов. [10] Эти два шаблона можно найти в многочисленных последующих книгах по языку C других авторов; [11] [12] [13] [14] в вводном учебнике 2007 года отмечается, что шаблон обработки ошибок — это способ обойти «отсутствие встроенной обработки исключений в языке C». [11] Другие программисты, включая разработчика и кодера ядра Linux Линуса Торвальдса или инженера-программиста и автора книг Стива Макконнелла , также возражают против точки зрения Дейкстры, утверждая, что GOTO могут быть полезной языковой функцией, улучшающей скорость программы, размер и ясность кода, но только при разумном использовании сравнительно разумным программистом. [15] [16] По словам профессора компьютерных наук Джона Регера , в 2013 году в коде ядра Linux было около 100 000 экземпляров goto. [17]
Другие ученые заняли более радикальную позицию и утверждали, что даже инструкции типа break
и return
из середины циклов являются плохой практикой, поскольку они не нужны в результате Бёма–Якопини, и поэтому выступали за то, чтобы циклы имели единственную точку выхода. [18] Например, Бертран Мейер написал в своем учебнике 2009 года, что инструкции типа break
и continue
«просто старье goto
в овечьей шкуре». [19] Однако слегка измененная форма результата Бёма–Якопини позволяет избегать дополнительных переменных в структурном программировании, пока разрешены многоуровневые разрывы циклов. [20] Поскольку некоторые языки, такие как C, не допускают многоуровневые разрывы с помощью своего break
ключевого слова, некоторые учебники советуют программистам использовать goto
в таких обстоятельствах. [14] Стандарт MISRA C 2004 запрещает goto
, continue
, а также множественные операторы return
и break
. [21] В издании стандарта MISRA C 2012 года запрет был понижен goto
с «обязательного» до «рекомендательного» статуса; в издании 2012 года есть дополнительное обязательное правило, которое запрещает только обратные, но не прямые прыжки с помощью goto
. [22] [23]
FORTRAN ввел структурные программные конструкции в 1978 году, и в последующих редакциях относительно свободные семантические правила, регулирующие допустимое использование goto, были ужесточены; «расширенный диапазон», в котором программист мог использовать GOTO для выхода и повторного входа в все еще выполняющийся цикл DO, был удален из языка в 1978 году, [24] а к 1995 году несколько форм Fortran GOTO, включая Computed GOTO и Assigned GOTO, были удалены. [25] Некоторые широко используемые современные языки программирования, такие как Java и Python, не имеют оператора GOTO — см. поддержку языка — хотя большинство из них предоставляют некоторые средства выхода из выделения или выхода или перехода к следующему шагу итерации. Точка зрения, что нарушение потока управления в коде нежелательно, может быть замечена в дизайне некоторых языков программирования, например, Ada [26] визуально подчеркивает определения меток с помощью угловых скобок .
Запись 17.10 в списке часто задаваемых вопросов comp.lang.c [27] напрямую рассматривает вопрос использования GOTO, утверждая:
Стиль программирования, как и стиль письма, является своего рода искусством и не может быть кодифицирован негибкими правилами, хотя дискуссии о стиле часто, кажется, сосредоточены исключительно вокруг таких правил. В случае оператора goto давно замечено, что неограниченное использование goto быстро приводит к неподдерживаемому спагетти-коду. Однако простой, бездумный запрет на оператор goto не обязательно приводит немедленно к красивому программированию: неструктурированный программист так же способен построить византийскую путаницу без использования goto (возможно, заменив вместо этого странно вложенные циклы и булевы управляющие переменные). Многие программисты занимают умеренную позицию: goto обычно следует избегать, но они приемлемы в нескольких хорошо ограниченных ситуациях, если это необходимо: как многоуровневые операторы break, для объединения общих действий внутри оператора switch или для централизации задач очистки в функции с несколькими возвратами ошибок. (...) Слепое избегание определенных конструкций или следование правилам без их понимания может привести к такому же количеству проблем, которое правила должны были предотвратить. Более того, многие мнения о стиле программирования являются всего лишь мнениями. Они могут быть сильно аргументированы и сильно прочувствованы, они могут быть подкреплены кажущимися вескими доказательствами и аргументами, но противоположные мнения могут быть столь же сильно прочувствованы, поддержаны и аргументированы. Обычно бесполезно втягиваться в «войны стилей», потому что по определенным вопросам оппоненты, похоже, никогда не могут согласиться, или согласиться не согласиться, или прекратить спорить.
Хотя общее использование goto снижается, в некоторых языках все еще существуют ситуации, когда goto обеспечивает самый короткий и простой способ выразить логику программы (хотя можно выразить ту же логику без goto, эквивалентный код будет длиннее и часто более сложным для понимания). В других языках существуют структурированные альтернативы, в частности исключения и хвостовые вызовы.
Ситуации, в которых goto часто полезен, включают в себя:
Такое использование довольно распространено в C, но гораздо менее распространено в C++ или других языках с более высокоуровневыми возможностями. [34] Однако в некоторых языках выдача и перехват исключения внутри функции могут быть чрезвычайно неэффективны; ярким примером является Objective-C , где goto является гораздо более быстрой альтернативой. [37]
Другое применение операторов goto — изменение плохо структурированного устаревшего кода , где избегание goto потребовало бы обширного рефакторинга или дублирования кода . Например, при наличии большой функции, где интерес представляет только определенный код, оператор goto позволяет перейти только к соответствующему коду или из него, не изменяя функцию каким-либо иным образом. Такое использование считается запахом кода [38] , но иногда находит применение.
Современное понятие подпрограммы было изобретено Дэвидом Уиллером при программировании EDSAC . Чтобы реализовать вызов и возврат на машине без инструкции вызова подпрограммы, он использовал специальный шаблон самомодифицирующегося кода, известный как прыжок Уиллера . [39] Это привело к возможности структурировать программы с использованием хорошо вложенных исполнений подпрограмм, взятых из библиотеки. Это было бы невозможно с использованием только goto
, поскольку целевой код, взятый из библиотеки, не знал бы, куда вернуться.
Позднее высокоуровневые языки, такие как Pascal, были разработаны с учетом поддержки структурного программирования , которое обобщало подпрограммы (также известные как процедуры или функции) в направлении дополнительных структур управления , таких как:
while
, repeat until
илиdo
, иforswitch
также известные как case
операторы, форма многоканального ветвленияЭти новые языковые механизмы заменили эквивалентные потоки, которые ранее записывались с использованием goto
s и if
s. Многоканальное ветвление заменяет «вычисляемый goto», в котором инструкция для перехода определяется динамически (условно).
При определенных условиях можно исключить локальные операторы goto устаревших программ, заменив их многоуровневыми операторами выхода из цикла. [40]
На практике строгое следование базовому шаблону структурного программирования из трех структур приводит к высоковложенному коду из-за невозможности преждевременного выхода из структурированного блока и комбинаторному взрыву с довольно сложными данными о состоянии программы для обработки всех возможных условий.
Обычно принимаются два решения: способ преждевременного выхода из структурированного блока и, в более общем смысле, исключения — в обоих случаях они поднимаются по структуре, возвращая управление вложенным блокам или функциям, но не переходят в произвольные места кода. Они аналогичны использованию оператора return в нетерминальной позиции — не строго структурированному из-за раннего выхода, а умеренному смягчению ограничений структурного программирования. В C break
и continue
позволяют завершить цикл или перейти к следующей итерации , не требуя дополнительного оператора while
or if
. В некоторых языках также возможны многоуровневые разрывы. Для обработки исключительных ситуаций были добавлены специализированные конструкции обработки исключений , такие как try
/ catch
/ finally
в Java.
Механизмы обработки исключений throw-catch также могут быть легко использованы для создания непрозрачных структур управления, точно так же, как может быть использован goto. [41]
В докладе, представленном на конференции ACM в Сиэтле в 1977 году, Гай Л. Стил подвел итоги дебатов по поводу GOTO и структурного программирования и заметил, что вызовы процедур в хвостовой позиции процедуры могут быть наиболее оптимально рассмотрены как прямая передача управления вызываемой процедуре, как правило, исключая ненужные операции манипуляции стеком. [42] Поскольку такие «хвостовые вызовы» очень распространены в Lisp , языке, где вызовы процедур повсеместны, эта форма оптимизации значительно снижает стоимость вызова процедуры по сравнению с GOTO, используемым в других языках. Стил утверждал, что плохо реализованные вызовы процедур привели к искусственному восприятию того, что GOTO был дешевым по сравнению с вызовом процедуры. Стил далее утверждал, что «в целом вызовы процедур можно полезно рассматривать как операторы GOTO, которые также передают параметры и могут быть единообразно закодированы как инструкции JUMP машинного кода », при этом инструкции манипуляции стеком машинного кода «считаются оптимизацией (а не наоборот!)». [42] Стил привел доказательства того, что хорошо оптимизированные численные алгоритмы в Lisp могли выполняться быстрее, чем код, созданный тогдашними коммерческими компиляторами Fortran, поскольку стоимость вызова процедуры в Lisp была намного ниже. В Scheme , диалекте Lisp, разработанном Стилом с Джеральдом Джеем Сассманом , оптимизация хвостового вызова является обязательной. [43]
Хотя статья Стила не внесла много нового в информатику, по крайней мере, в том виде, в котором она практиковалась в MIT, она пролила свет на возможности оптимизации вызовов процедур, что сделало качества процедур, способствующие модульности, более надежной альтернативой распространенным тогда привычкам кодирования больших монолитных процедур со сложными внутренними структурами управления и обширными данными о состоянии. В частности, оптимизация хвостовых вызовов, обсуждаемая Стилом, превратила процедуру в надежный способ реализации итерации посредством одиночной хвостовой рекурсии (хвостовая рекурсия, вызывающая одну и ту же функцию). Кроме того, оптимизация хвостовых вызовов допускает взаимную рекурсию неограниченной глубины, предполагая хвостовые вызовы — это позволяет передавать управление, как в конечных автоматах , что в противном случае обычно достигается с помощью операторов goto.
Сопрограммы являются более радикальным ослаблением структурного программирования, допуская не только множественные точки выхода (как при возврате в не-хвостовой позиции), но и множественные точки входа, аналогично операторам goto. Сопрограммы более ограничены, чем goto, поскольку они могут возобновлять текущую запущенную сопрограмму только в указанных точках — продолжая после yield — а не переходя к произвольной точке в коде. Ограниченной формой сопрограмм являются генераторы , которых достаточно для некоторых целей. Еще более ограниченными являются замыкания — подпрограммы, которые сохраняют состояние (через статические переменные ), но не позицию выполнения. Комбинация переменных состояния и структурированного управления, в частности, общий оператор switch, может позволить подпрограмме возобновлять выполнение в произвольной точке при последующих вызовах и является структурированной альтернативой операторам goto при отсутствии сопрограмм; это распространенная идиома в C, например.
Продолжение похоже на GOTO в том, что оно переносит управление из произвольной точки программы в ранее отмеченную точку. Продолжение более гибко, чем GOTO, в тех языках, которые его поддерживают, поскольку оно может передавать управление из текущей функции, чего GOTO не может сделать в большинстве структурированных языков программирования. В тех реализациях языка, которые поддерживают стековые фреймы для хранения локальных переменных и аргументов функций, выполнение продолжения включает в себя настройку стека вызовов программы в дополнение к переходу. Функция longjmp языка программирования C является примером escape-продолжения, которое может использоваться для выхода из текущего контекста в окружающий. Оператор GO в Common Lisp также имеет это свойство разматывания стека, несмотря на то, что конструкция имеет лексическую область видимости , поскольку на метку, к которой нужно перейти, можно ссылаться из замыкания .
В Scheme продолжения могут даже перемещать управление из внешнего контекста во внутренний, если это необходимо. Этот почти безграничный контроль над тем, какой код будет выполнен следующим, делает сложные структуры управления, такие как сопрограммы и кооперативная многозадачность, относительно простыми для написания. [43]
В непроцедурных парадигмах goto менее актуален или вообще отсутствует. Одной из основных альтернатив является передача сообщений , которая имеет особое значение в параллельных вычислениях , межпроцессном взаимодействии и объектно-ориентированном программировании . В этих случаях отдельные компоненты не имеют произвольной передачи управления, но общее управление может быть запланировано сложными способами, например, через вытеснение . Влиятельные языки Simula и Smalltalk были среди первых, кто ввел концепции сообщений и объектов. Инкапсулируя данные о состоянии, объектно-ориентированное программирование уменьшило сложность программного обеспечения до взаимодействий (сообщений) между объектами.
В классе операторов goto имеется ряд различных языковых конструкций .
В Fortran вычисляемый переходит к одной из нескольких меток в списке, основываясь на значении выражения. Примером является . [44] Эквивалентная конструкция в C — это оператор switch , а в более новом Fortran конструкция является рекомендуемой синтаксической альтернативой. [45] В GOTO
BASIC был оператор, который достигал той же цели, но в Visual Basic эта конструкция больше не поддерживается. [46]goto (20,30,40) i
SELECT CASE
'On GoTo'
В версиях до Fortran 95 в Fortran также был назначенный вариант goto , который передавал управление метке оператора (номеру строки), которая хранится в (назначается) целочисленной переменной. Переход к целочисленной переменной, которой не был назначен ASSIGNED, к сожалению, был возможен и был основным источником ошибок, связанных с назначенными goto. [47] Оператор Fortran assign
допускает назначение целочисленной переменной только постоянного (существующего) номера строки. Однако некоторые компиляторы допускали случайную обработку этой переменной как целого числа впоследствии, например, ее увеличение, что приводило к неопределенному поведению в goto
момент времени. Следующий код демонстрирует поведение оператора, goto i
когда строка i не определена:
назначить 200 для i i = i + 1 перейти к i ! неопределенное поведение 200 запись ( * , * ) "это допустимый номер строки"
Несколько компиляторов C реализуют два нестандартных расширения C/C++, связанных с goto, изначально представленных gcc . [48] Расширение GNU позволяет получить адрес метки внутри текущей функции как void*
с помощью унарного префиксного оператора значения метки &&
. Инструкция goto также расширена, чтобы разрешить переход к произвольному void*
выражению. Это расширение C упоминается как вычисляемый goto в документации компиляторов C, которые его поддерживают; его семантика является надмножеством назначенного goto Fortran, поскольку оно допускает произвольные выражения указателя в качестве цели goto, в то время как назначенный goto Fortran не допускает произвольные выражения в качестве цели перехода. [49] Как и в случае со стандартным goto в C, расширение GNU C позволяет цели вычисляемого goto находиться только в текущей функции. Попытка перехода за пределы текущей функции приводит к неопределенному поведению. [49]
Некоторые варианты BASIC также поддерживают вычисляемый GOTO в том смысле, который используется в GNU C, то есть в котором целью может быть любой номер строки, а не только один из списка. Например, в MTS BASIC можно написать GOTO i*1000
, чтобы перейти к строке с номером, в 1000 раз превышающим значение переменной i (которая может представлять выбранную опцию меню, например). [50]
Переменные меток PL/I достигают эффекта вычисляемых или назначенных GOTO
s.
До 1985 года в стандарте ANSI COBOL существовал оператор ALTER, который можно было использовать для изменения назначения существующего GO TO, который должен был находиться в отдельном абзаце. [51] Эта функция, допускающая полиморфизм , часто осуждалась и редко использовалась. [52]
В Perl есть вариант оператора goto
, который вообще не является традиционным оператором GOTO. Он принимает имя функции и передает управление, эффективно заменяя один вызов функции другим ( хвостовой вызов ): новая функция не вернется к GOTO, а вместо этого к месту, из которого была вызвана исходная функция. [53]
Есть несколько языков программирования, которые не поддерживают GOTO по умолчанию. Используя эмуляцию GOTO, все еще возможно использовать GOTO в этих языках программирования, хотя и с некоторыми ограничениями. Можно эмулировать GOTO в Java, [54] JavaScript, [55] и Python. [56] [57]
PL/I имеет тип данных LABEL , который может использоваться для реализации как "назначенного goto", так и "вычисляемого goto". PL/I допускает переходы из текущего блока. Вызывающая процедура может передать метку в качестве аргумента вызываемой процедуре, которая затем может выйти с переходом. Значение переменной метки включает адрес кадра стека, а goto из блока выталкивает стек.
/* Это реализует эквивалент */ /* назначенный goto */ указать, где находится этикетка; где = где-то; идти куда; ... где-то: /* оператор */ ; ...
/* Это реализует эквивалент */ /* вычисленный goto */ объявить, где (5) этикетка; объявить inx исправленным; где(1) = abc; где(2) = xyz; ... перейти туда, где(inx); ... abc: /* оператор */ ; ... xyz: /* оператор */ ; ...
Более простой способ получить эквивалентный результат — использовать массив констант меток , для которого даже не требуется явное объявление переменной типа LABEL :
/* Это реализует эквивалент */ /* вычисленный goto */ объявить inx исправленным; ... перейти туда, где(inx); ... где(1): /* оператор */ ; ... где(2): /* оператор */ ; ...
В пакетном файле DOS Goto направляет выполнение на метку, которая начинается с двоеточия. Целью Goto может быть переменная.
@ echo off SET D8str = %date% SET D8dow = %D8str:~0,3%ДЛЯ %% D в ( Пн Ср Пт ) do if " %% D" == " %D8dow% " goto SHOP%%D echo Сегодня, %D8dow% , не день покупок. goto end: SHOPMon echo купить пиццу на обед - понедельник - День пиццы. goto end: SHOPWed echo купить Кальцоне, чтобы забрать домой - сегодня среда. goto конец: SHOPFri echo купить Seltzer на случай, если кто-то захочет напиток с нулевым содержанием калорий. : конец
Многие языки поддерживают этот goto
оператор, а многие — нет. В Javagoto
— зарезервированное слово , но его нельзя использовать, хотя скомпилированные .class
файлы генерируют GOTO и LABEL. [58] В Python нет поддержки goto, хотя есть несколько шуточных модулей, которые ее предоставляют. [ 56] [57] В Seed7 нет оператора goto , а скрытые goto, такие как break- и continue-statements, также опущены. [59] В PHP не было собственной поддержки goto
до версии 5.3 (библиотеки были доступны для эмуляции его функциональности). [60]
C# и Visual Basic .NET поддерживают goto
. [61] [62] Однако он не позволяет переходить к метке за пределами текущей области действия и уважает утилизацию объектов и конструкции finally, что делает его значительно менее мощным и опасным, чем goto
ключевое слово в других языках программирования. Он также создает метки операторов case и default , областью действия которых является охватывающий оператор switch ; goto case или goto default часто используется в качестве явной замены неявного прохода, который C# запрещает.
В языке программирования PL /I есть оператор GOTO, который раскручивает стек для передачи из блока и не допускает передачу в блок из-за его пределов.
Другие языки могут иметь свои собственные отдельные ключевые слова для явных провалов, которые можно считать версией, goto
ограниченной для этой конкретной цели. Например, Go использует fallthrough
ключевое слово и вообще не допускает неявного провала, [63] в то время как Perl 5 использует next
для явного провала по умолчанию, но также позволяет устанавливать неявный провал как поведение по умолчанию для модуля.
Большинство языков, в которых есть операторы goto, называют его так, но на заре вычислений использовались другие названия. Например, в MAD использовался оператор TRANSFER TO. [64] APL использует стрелку, указывающую вправо, →
для goto.
В языке C есть goto, и он широко используется в различных идиомах, как обсуждалось выше.
Функциональные языки программирования, такие как Scheme, обычно не имеют goto, вместо этого используются продолжения.
{{cite web}}
: CS1 maint: числовые имена: список авторов ( ссылка ){{cite web}}
: CS1 maint: неподходящий URL ( ссылка )(иногда называемая WWG , по инициалам ее авторов) была первой книгой по программированию компьютеров
В этом документе описываются синтаксис, семантика и реализация IBM z/OS XL C/C++ языков программирования C и C++. Для справочника по стандартам C или C++ общего назначения см. cppreference.com.
{{cite web}}
: CS1 maint: числовые имена: список авторов ( ссылка )