GOTO

GOTO

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

Содержание

Функциональность

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

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

Распространение

GOTO имеется в таких языках, как Фортран, Алгол, КОБОЛ, Бейсик, Си, C++, C#, D, Паскаль, Perl, Ада, PHP, а также во многих других. GOTO присутствует также во всех языках ассемблера в форме JMP, JUMP или BRA (от англ. branch — ветвь) и используется там чрезвычайно активно. Свобода использования оператора GOTO в различных языках сильно различается. Если в ассемблерах или языках типа Фортрана он может применяться произвольно (допускается передача управления внутрь ветви условного оператора или внутрь тела цикла, а иногда и процедуры), то в более поздних языках высокого уровня его использование ограничено: как правило, с помощью GOTO запрещено передавать управление между различными процедурами и функциями, внутрь выделенного блока операторов, между ветвями условного оператора и оператора множественного выбора.

GOTO отсутствует в некоторых языках высокого уровня, например в Forth (но может быть реализовано средствами самого языка). В Паскаль GOTO первоначально включён не был, но недостаточность имеющихся языковых средств вынудила Никлауса Вирта его добавить. В более поздних своих языках Вирт всё же отказался от GOTO: этого оператора нет ни в Модуле-2, ни в Обероне и Компонентном Паскале. В Java есть зарезервированное слово goto, но оно не несёт никаких функций — оператора безусловного перехода в языке нет. Однако переход осуществить можно. При этом в языке сохранились метки — они могут применяться для выхода из вложенных циклов операторами break и continue.

Критика

Оператор GOTO в языках высокого уровня является объектом критики, поскольку чрезмерное его применение приводит к созданию нечитаемого «спагетти-кода». Впервые эта точка зрения была отражена в статье Эдсгера Дейкстры «Доводы против оператора GOTO»[1], который заметил, что качество программного кода обратно пропорционально количеству операторов GOTO в нём. Статья приобрела широкую известность как среди теоретиков, так и среди практиков программирования, в результате чего взгляды на использование оператора GOTO были существенно пересмотрены. В своей следующей работе Дейкстра обосновал тот факт, что для кода без GOTO намного легче проверить формальную корректность.

Код с GOTO трудно форматировать, так как он может нарушать иерархичность выполнения (то есть парадигму структурного программирования), и потому отступы, призванные отображать структуру программы, не всегда могут быть выставлены правильно. GOTO также аннулирует многие возможности компилятора по оптимизации управляющих структур[2].

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

  • В языках, имеющих блочную структуру, допускающую описание в каждом блоке своих локальных переменных (например, в C или C++), передача управления внутрь блока «обходит» часть описаний этих переменных. Соответственно, может быть пропущено выделение памяти и вызов конструкторов для некоторых локальных переменных. По завершении блока будет происходить (в случае C++) вызов деструкторов для всех объявленных в блоке переменных, после чего — автоматическое удаление локальных переменных из памяти. В результате, как минимум, будут вызваны деструкторы для переменных, для которых не вызывались конструкторы, а в худшем случае произойдёт попытка освобождения не выделенной памяти, что вызовет ошибку.
  if (a > 0) {goto inner};
  ... // какие-то команды
  {
    X ax = X(a);
    ... // какие-то команды
  inner:
    // Сюда произойдёт переход по goto
    ...
    // По завершении всех команд блока компилятор вызовет 
    // деструктор ~X() для переменной ax.
  }
В вышеприведённом примере в случае перехода по goto в конце блока, выделенного фигурными скобками, будет вызван деструктор для ax, хотя конструктор для этой переменной не вызывался.
  • Передача управления внутрь тела цикла приводит к пропуску кода инициализации цикла или первоначальной проверки условия. Последствия непредсказуемы.
  • Передача управления между ветвями условного оператора приводит к тому, что выполняется часть команд, соответствующих выполнению условия, и часть команд, соответствующих его ложности.
  • Передача управления внутрь процедуры или функции может приводить к непредсказуемым последствиям. Поскольку правильная команда вызова процедуры не выполнялась, на вершине стека возврата находится адрес возврата из той процедуры, откуда вызвался GOTO, за которым, возможно, находится содержимое локальных переменных процедуры, если они размещаются в стеке. В результате, если только такая ситуация не обрабатывается компилятором специально, по завершении процедуры или функции, внутрь которой произведён переход, произойдёт разрушение стека и переход по непредсказуемому адресу.

Доводы против оператора GOTO оказались столь серьёзны, что в структурном программировании его стали рассматривать как крайне нежелательный. Это нашло своё отражение при проектировании новых языков программирования. Например, GOTO был намеренно полностью запрещён в Java и Ruby. Вместе с тем, в ряде современных языков он оставлен из соображений эффективности кодирования в тех редких случаях, когда применение GOTO оправданно. Так, GOTO сохранился в Аде — одном из наиболее продуманных с точки зрения архитектуры языке за всю историю[3]. Однако в тех современных языках высокого уровня, где этот оператор существует, на его использование, как правило, накладываются жёсткие ограничения, препятствующие использованию наиболее опасных методов его применения. В частности, как правило, категорически запрещается передавать управление извне процедуры или функции внутрь неё, извне цикла — внутрь его тела, из одной ветви условного оператора или оператора-переключателя — в другую его ветвь. ANSI-стандарт языка C++ запрещает обход инициализации переменной с помощью GOTO (то есть фрагмент кода, приведённый выше, современным транслятором, например, gcc 4.5, будет отвергнут как синтаксически некорректный). Встречаются и более жёсткие ограничения, например, запрет на передачу управления по GOTO внутрь любого выделенного блока в программе извне этого блока.

Формально доказано (теорема Бома-Якопини), что применение GOTO не является обязательным, то есть не существует такой программы с GOTO, которую нельзя было бы переписать без него с полным сохранением функциональности (однако с потерями эффективности (см. ниже)).

Оправданное применение

Тем не менее, в практическом программировании применение GOTO в некоторых случаях можно считать допустимым. Поскольку GOTO — «простейший», «атомарный» оператор перехода, а все остальные являются «составными», производными от него, то применение GOTO допустимо и оправданно, когда другие средства языка не реализуют или недостаточно эффективно реализуют нужную функциональность. К таким случаям можно отнести:

Выход из нескольких вложенных циклов сразу

Обычно считается, что в языках, где операторы досрочного завершения цикла (такие, как break и continue в Си) могут относиться только к тому из вложенных циклов, в котором они расположены, использование goto допустимо, чтобы выйти из нескольких вложенных циклов сразу. Здесь GOTO значительно упрощает программу, избавляя от необходимости создания вспомогательных переменных-флагов и условных операторов.

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

Пример:

int matrix[n][m];
int value;
...
for(int i=0; i<n; i++)
  for (int j=0; j<m; j++)
    if (matrix[i][j] == value)
    {
      printf("value %d found in cell (%d,%d)\n",value,i,j);
      //act if found
      goto end_loop;
    }
printf("value %d not found\n",value);
//act if not found
end_loop: ;

Прямолинейный способ избавления от GOTO — создать дополнительную переменную-флаг, сигнализирующую, что надо выйти из внешнего цикла (после выхода из внутреннего по break) и обойти блок кода, выполняющийся, когда значение не найдено. Но вряд ли этот способ можно рекомендовать на практике, так как в результате код окажется загромождён проверками, станет длиннее и будет дольше работать. Но можно вынести код в функцию и использовать return.

Без изменения структуры кода проблема решается, если команда break (или её аналог) позволяет выйти из нескольких вложенных блоков сразу, как в Java или Ada. Аналогичный код на Java никакого goto не требует:

int[][] matrix;
int value;
...
outer: {
  for(int i=0; i<n; i++)
    for (int j=0; j<m; j++)
      if (matrix[i][j] == value)
      {
        System.out.println("value " + value + " found in cell (" + i + "," + j + ")");
        break outer;
      }
  System.out.println("value " + value + " not found");
}

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

Обработка ошибок

Этот случай применим к языкам, не содержащим конструкции try ... finally — например, к C без применения SEH, существующего только в Windows. В этом случае goto используется для перехода на код «очистки» — находящийся в конце функции и уничтожающий созданные ею объекты перед выходом из неё. Этот метод широко используется при написании драйверов.

Пример такой обработки ошибок (все имена и константы, кроме NULL, вымышлены и приведены лишь для примера):

int fn( int* presult )
{
  int sts = 0;
  TYPE entity, another_entity = NULL;
  TYPE2 entity2 = NULL;
 
  if ( !( entity = create_entity() ) )
    { sts = ERROR_CODE1; goto exit0; }
 
  if ( !do_something( entity ) )
    { sts = ERROR_CODE2; goto exit1; }
 
  if ( condition ) {
    if ( !( entity2 = create_another_entity() ) )
      { sts = ERROR_CODE3; goto exit1; }
 
    if ( ( *presult = do_another_thing( entity2 ) == NEGATIVE )
      { sts = ERROR_CODE4; goto exit2; }
  } 
  else {
    if ( ( *presult = do_something_special( entity ) == NEGATIVE )
      { sts = ERROR_CODE5; goto exit2; }
  }
  exit2: if ( entity2 ) destroy_another_entity( entity2 );
  exit1: destroy_entity( entity );
  exit0: return sts;
}

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

Главным критерием применимости goto во всех случаях, включая указанные, является ненарушение используемой парадигмы программирования. В приведенных примерах это — структурное программирование, то есть должны сохраняться иерархическая организация программы и таковая же логика её работы. Нарушение принципа иерархии (например: переходы внутрь цикла; обход операций инициализации — как явных, так и неявных; выход из кода, следующего за fork(), в код, предшествующий ему) чревато всевозможными побочными эффектами, возникающими из деталей трансляции программы в машинный код, и, как следствие, странными, труднообнаружимыми ошибками.

Автогенерация кода

Ещё одним допустимым применением безусловного перехода является код, который генерируется автоматически, например, генерируемые с помощью программных инструментальных средств лексические и синтаксические анализаторы. Например, код, генерируемый утилитами yacc, lex, bison изобилует командами goto, но в этом нет ничего плохого, так как этот код в принципе не предназначен для восприятия и редактирования человеком, а его корректность целиком определяется корректностью создающего его инструмента. Иначе говоря, здесь имеет место та же самая ситуация, что в случае с компилятором языка высокого уровня, создающим машинный код (с неизбежными командами безусловного перехода) просто потому, что таков целевой язык.

См. также

  • Теорема Бома — Якопини

Примечания

Ссылки


Wikimedia Foundation. 2010.

Игры ⚽ Нужно решить контрольную?

Полезное


Смотреть что такое "GOTO" в других словарях:

  • GOTO — o GO TO (ir a en inglés) es una instrucción muy común en los lenguajes de programación con el objetivo de controlar el flujo del programa. El efecto de su versión más simple es transferir sin condiciones la ejecución del programa a la etiqueta o… …   Wikipedia Español

  • Goto — is a statement found in many computer programming languages. It is a combination of the English words and . When executed it causes an unconditional transfer of control (a jump ) to another statement. The jumped to statement is specified using… …   Wikipedia

  • goto — (от англ. go to  «перейти к»)  оператор безусловного перехода (перехода к определённой точке программы, обозначенной номером строки либо меткой) в некоторых языках программирования. В некоторых языках оператор безусловного перехода …   Википедия

  • GOTO — L instruction goto (de l anglais go to, en français aller à) est une instruction présente dans de nombreux langages de programmation. Elle est utilisée pour réaliser des sauts inconditionnels dans un programme. L exécution est renvoyée vers une… …   Wikipédia en Français

  • Gotō — Goto steht für: Sprunganweisung, einen Sprung in der Programmierung Goto (Teleskop), eine Vorrichtung an Teleskopen Verein der Künste Hauptsitz in Österreich Gotō (五島) steht außerdem für: Gotō Inseln, eine Inselgruppe in der japanischen Präfektur …   Deutsch Wikipedia

  • Goto — steht für: Sprunganweisung, einen Sprung in der Programmierung Goto (Teleskop), eine Vorrichtung an Teleskopen Gotō (五島) steht außerdem für: Gotō Inseln, eine Inselgruppe in der japanischen Präfektur Nagasaki Gotō (Nagasaki), eine Stadt auf… …   Deutsch Wikipedia

  • Gotō — shi (五島市) Plage d Omaha à Gotō Administration Pays Japon Région Kyūshū Préfecture …   Wikipédia en Français

  • Goto — puede referirse a: GOTO (ir a), instrucción de varios lenguajes de programación; Gotō, apellido japonés; Islas Gotō, archipiélago japonés. Esta página de desambiguación cataloga artículos relacionados con el mismo título. Si llegaste aquí a… …   Wikipedia Español

  • GOTO — o GO TO (ir a en inglés) es una sentencia o instrucción muy común en los lenguajes de programación con el objetivo de controlar el flujo del programa. El efecto de su versión más simple es transferir sin condiciones la ejecución del programa a la …   Enciclopedia Universal

  • goto — |ô| s. m. 1.  [Informal] Glote. 2. O que causa sufocação. 3. dar no goto: entrar na glote (a comida ou a bebida). 4.  [Figurado] Causar estranheza; agradar. • Plural: gotos |ô| …   Dicionário da Língua Portuguesa


Поделиться ссылкой на выделенное

Прямая ссылка:
Нажмите правой клавишей мыши и выберите «Копировать ссылку»