goto

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

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

Функциональность[править | править исходный текст]

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

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

Распространение[править | править исходный текст]

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

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

Критика[править | править исходный текст]

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

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

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

  • Если некоторая переменная инициализируется (получает значение) в одном месте и потом используется далее, то переход в точку после инициализации, но до использования, приведёт к тому, что будет использовано значение, которое находилось в памяти, выделенной под переменную, до момента выделения (и которое, как правило, является произвольным и случайным).
  • Передача управления внутрь тела цикла приводит к пропуску кода инициализации цикла или первоначальной проверки условия. Аналогично, передача управления внутрь процедуры или функции приводит к пропуску её предисловия (пролога), в котором производится инициализация (выделение памяти под локальные переменные и т. п.).

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

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

Оправданное применение[править | править исходный текст]

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

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

Выход из вложенных циклов[править | править исходный текст]

В некоторых языках нет операторов досрочного завершения цикла или они относятся только к тому из вложенных циклов, в котором расположены (например, break и continue в Си). Использование 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) и обойти блок кода, выполняющийся, когда значение не найдено.

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

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 может использоваться для прерывания «нормального» выполнения кода и перехода к завершающему коду для освобождения занятой памяти и прочих финальных действий. Пример на языке Си:

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 подобный код был бы излишне загромождён множеством дополнительных условных операторов if.

Автогенерация кода[править | править исходный текст]

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

В культуре[править | править исходный текст]

В сериале Футурама, в церкви роботов фигурируют афористичные высказывания, оформленные в стиле операторов языка Бейсик:

  • В одной из серий свадьба роботов проходила в церкви роботов, над алтарём которой была написана заповедь:
    10 SIN
    20 GOTO HELL

    (греши, потом иди в ад)
  • В серии «I, Roommate» в квартире Фрая и Бендера на картине есть надпись:
    10 HOME
    20 SWEET
    30 GOTO 10

    (дом, милый [дом, милый…])

Примечания[править | править исходный текст]

Ссылки[править | править исходный текст]