GOTO
GOTO (англ. go to — «перейти к») — в некоторых языках программирования — оператор безусловного перехода (перехода к определённой точке программы, обозначенной номером строки либо меткой). В более широком смысле, под «GOTO» подразумевают любой такой оператор, даже если в рассматриваемом языке он называется по-другому. В компилируемых языках GOTO можно рассматривать как основную операцию по передаче управления из одной части программы в другую, поскольку компилятор переводит все остальные операторы перехода в форму, аналогичную GOTO.
Функциональность
В абсолютном большинстве языков программирования, поддерживающих его использование, оператор GOTO состоит из двух частей: собственно имени оператора и метки, маркирующей целевую точку перехода в программе, то есть имеет вид GOTO метка
. Метка, в зависимости от правил языка, может быть либо числом (как, например, в классическом Бейсике), либо правильным идентификатором используемого языка программирования. Чтобы оператор перехода был корректным, необходимо наличие в тексте программы места, помеченного той же самой меткой, которая использована в данном операторе. Пометка может выглядеть по-разному, например, в языке Паскаль она имеет вид метка:
(то есть имя метки, за которым следует двоеточие), возможны и другие соглашения.
Выполнение оператора перехода состоит в том, что следующим после него будет выполнен тот оператор программы, который стоит в тексте непосредственно за помеченным меткой местом (оператор, помеченный меткой), и далее будут последовательно выполняться операторы, расположенные после него (разумеется, до следующего оператора перехода, ветвления или цикла). В случае машинных языков (ассемблеров или непосредственно машинного кода) технический смысл команды перехода элементарен: она записывает в регистр процессора, хранящий адрес следующей выполняемой команды, адрес команды, помеченной меткой.
Распространение
GOTO имеется в таких языках, как Фортран, Алгол, КОБОЛ, Бейсик, Си, 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 оказались столь серьёзны, что в структурном программировании его стали рассматривать как крайне нежелательный. Это нашло своё отражение при проектировании новых языков программирования. Например, GOTO был намеренно полностью запрещён в Java и Ruby. Вместе с тем, в Аде — одном из наиболее продуманных с точки зрения архитектуры языке за всю историю[3], 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()) == NULL) {sts = ERROR_CODE1; goto exit0;}
if (!do_something(entity) ) {sts = ERROR_CODE2; goto exit1;}
if ( condition ) {
if ((entity2 = create_another_entity()) == NULL ) {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()
, в код, предшествующий ему) чревато всевозможными побочными эффектами, возникающими из деталей трансляции программы в машинный код, и, как следствие, странными, труднообнаружимыми ошибками.
Примечания
Ссылки
- goto в Pascal, Delphi (рус.)
- Code Complete: A Practical Handbook of Software Construction. Redmond, Wa.: Microsoft Press, 880 pages, 1993 (англ.)
- Carl Youngblood, An Argument for the Use of goto Statements (англ.)
ca:GOTO de:Sprunganweisung en:Goto es:GOTO fr:Goto he:פקודת goto hr:Goto hu:GOTO it:GOTO ja:Goto文 ko:GOTO문 nl:GOTO pl:Goto pt:Goto (programação) sh:GoTo sv:Goto tr:GOTO uk:Безумовний перехід zh:Goto
Если вам нравится SbUP.com Сайт, вы можете поддержать его - BTC: bc1qppjcl3c2cyjazy6lepmrv3fh6ke9mxs7zpfky0 , TRC20 и ещё....