8-900-374-94-44
[email protected]
Slide Image
Меню

Си goto – Урок №66. Оператор goto | Уроки С++

Урок №66. Оператор goto | Уроки С++

  Обновл. 26 Апр 2019  | 

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

#include <iostream> #include <cmath> // для функции sqrt() int main() { double z; tryAgain: // это лейбл std::cout << «Enter a non-negative number: «; std::cin >> z; if (z < 0.0) goto tryAgain; // а это оператор goto std::cout << «The sqrt of » << z << » is » << sqrt(z) << std::endl; return 0; }

#include <iostream>

#include <cmath> // для функции sqrt()

int main()

{

    double z;

tryAgain: // это лейбл

    std::cout << «Enter a non-negative number: «;

    std::cin >> z;

    if (z < 0.0)

        goto tryAgain; // а это оператор goto

    std::cout << «The sqrt of » << z << » is » << sqrt(z) << std::endl;

    return 0;

}

В этой программе пользователю предлагается ввести неотрицательное число. Однако, если пользователь введёт отрицательное число, то программа, используя оператор goto, выполнит переход обратно к лейблу tryAgain. Затем пользователю снова нужно будет ввести число. Таким образом, мы можем постоянно запрашивать у пользователя ввод числа, пока он не введёт корректное число.

Ранее мы рассматривали два типа области видимости: локальная (или ещё «блочная») и глобальная (или ещё «файловая»). Лейблы используют третий тип области видимости:

область видимости функции. Оператор goto и соответствующий лейбл должны находиться в одной и той же функции.

Существуют некоторые ограничения на использование операторов goto. Например, вы не сможете перепрыгнуть вперёд через переменную, которая инициализирована в том же блоке, что и goto:

int main() { goto skip; // прыжок вперёд недопустим int z = 7; skip: // лейбл z += 4; // какое значение будет в этой переменной? return 0; }

int main()

{

    goto skip; // прыжок вперёд недопустим

    int z = 7;

skip: // лейбл

    z += 4; // какое значение будет в этой переменной?

    return 0;

}

В целом, программисты избегают использования оператора goto в C++ (и в большинстве других высокоуровневых языков программирования). Основная проблема с ним заключается в том, что он позволяет программисту управлять выполнением кода так, что точка выполнения может прыгать по коду произвольно. А это, в свою очередь, создаёт то, что опытные программисты называют «спагетти-кодом».

Спагетти-код — это код, порядок выполнения которого напоминает тарелку со спагетти (всё запутано и закручено), что крайне затрудняет следованию и пониманию логике выполнения такого кода.

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

Оператор goto часто используется в некоторых старых языках, таких как Basic или Fortran, или даже в C. Однако в C++ goto почти никогда не используется, поскольку любой код, написанный с ним, можно более эффективно переписать с использованием других объектов в C++, таких как циклы, обработчики исключений или деструкторы (всё из этого мы рассмотрим несколько позже).

Правило: Избегайте использования операторов goto, если на это нет веской причины.

Оценить статью:

Загрузка…

За репост +20 к карме и моя благодарность!

ravesli.com

Операторы переходов – язык программирования Си

Данная категория операторов применяется относительно редко. К их числу относятся: «break», «continue», «goto» и «return». Считается, что слишком частое использование операторов переходов ухудшает читаемость программы и затрудняет её модификацию. Но, все же, бывают моменты, когда без операторов, организующих переходы, — «как без воды».

Оператор «break» (в переводе с англ. «прерывать»).

Этот старый знакомый уже встречался при изучении оператора «switch» на Рис. 6.22, б, в. Именно он организовывал досрочный выход из процедуры множественного сравнения «кейсов».

Оператор «break» в одиночку бесполезен. Его надо вводить внутрь тела одного из четырёх операторов: «switch», «while», «do-while», «for». При помощи оператора «break» досрочно прерывается выполнение основного цикла и пропускаются оставшиеся команды, после чего управление передаётся вниз к самому первому оператору после закрывающей фигурной скобки.

Если оператор «break» находится внутри нескольких вложенных друг в друга циклов, то его действие распространяется только на внутренний цикл, т.е. на тот, в котором он содержится. Именно из-за этого нюанса у программистов возникают трудности и ошибки при отлаживании больших по объёму листингов.

Интересное наблюдение. Оператор «break» является абсолютно прозрачным по отношению к операторам выбора «if» и «if-else». Он беспрепятственно «проходит» сквозь их фигурные скобки, что надо учитывать на практике.

Оператор «continue» (в переводе с англ. «продолжать»).

Сфера применения оператора «continue» меньше, чем у оператора «break». Он работаетлишь с тремя циклами: «while», «do-while», «for» и является прозрачным для операторов «if», «if-else». Главное различие между «continue» и «break» заключается в том, что вместо досрочного завершения цикла осуществляется досрочное завершение текущей итерации с пропуском оставшихся команд. Это более мягкое условие, не приводящее сразу к окончанию цикла (Рис. 6.26, а, б).

Рис. 6.26. Сравнение циклов «for»: а) с оператором «continue»; б) с оператором «break». Оператор «goto» (в переводе с англ. «перейти к»).

Оператор «goto» состоит из двух частей — ключевого слова «goto» и метки. Имена меток образуются по тем же правилам, что и имена переменных. Например, при выполнении оператора «goto metka;» управление передаётся на тот оператор, в начале которого слева указано ключевое слово «metka:». Оно может находиться влюбом, самом неожиданном месте программы. При этом не обращается внимание ни на вложенные циклы, ни на условия проверки, ни на незавершённые вычисления. Переход производится напрямую в любую строку программы, причём без задержек во времени и без окончания текущих операций.

Вот тут-то и кроется «терра инкогнита» для начинающих программистов, поскольку надо иметь большой опыт, чтобы учесть все последствия, возникающие после выхода из прерванной процедуры при неизвестно чем заполненном стеке и загадочном состоянии регистров специальных функций.

Чтобы избежать путаницы, разработаны стандартные приёмы, которые позволяют заменить все без исключения конструкции «goto» связками обычных Сператоров. Конкретные примеры приведены в [6-6]. Родоначальники языка Си, Б.Керниган и Д.Ритчи, тоже призывают применять оператор «goto» как можно реже или не применять его совсем [6-7].

Существует лишь один частный случай, когда оператор «goto» полезен — это быстрый выход из многократно вложенных циклов при обнаружении каких-либо аварийных или внештатных ситуаций. Оператор «break» здесь пасует, поскольку даёт возможность выхода только из самого внутреннего цикла, а вот оператор «goto» — из любого, в том числе из бесконечного.

На Рис. 6.27, а, б показаны каркасы программ, моделирующие безусловные переходы вверх и вниз. В каком бы глубоком цикле не находился оператор «goto», управление будет сразуже передано на метку «sl:» (имя выбирается произвольно). Если метка указывает на первый оператор программы, то произойдёт «мягкий» сброс MK. В этом случае разработчику надо предусмотреть начальную инициализацию всех без исключения используемых в программе регистров специальных функций и чёткую установку всех задействованных по схеме линий портов.

Рис. 6.27. Форма записи оператора «goto»: а) с переходом вверх; б) с переходом вниз.

В языке Basic тоже существует оператор «GO ТО», причём применяется он настолько часто, что листинги программ аж пестрят прямыми переходами. В языке Си, наоборот, от «goto» стараются избавляться разными способами. Очевидно, что любителям Basic придётся адаптироваться к такому стилю программирования.

Оператор «return» (в переводе с англ. «возвращаться»).

Этот оператор несколько обособлен от своих сородичей, поскольку применяется без ограничений. Его роль — организация корректного выхода из функций. Именно о них и пойдёт речь дальше.

Источник: Рюмик, С. М., 1000 и одна микроконтроллерная схема. Вып. 2 / С. М. Рюмик. — М.:ЛР Додэка-ХХ1, 2011. — 400 с.: ил. + CD. — (Серия «Программируемые системы»).

nauchebe.net

GOTO — Lurkmore

Сферичнее только 10 GOTO 10

GOTO (гоу ту, бдлкдр. гото) — слитное написание английского go to, что переводится как «иди к…» либо «иди на…», оператор безусловного перехода к указанной после него метке/строке программы. Причина лютых срачей между программистами. Споры на тему «дозволено ли GOTO или нет» (можно ли ТАКОЕ употреблять в программах) идут с самого начала существования структурного программирования.

При ненадлежащем использовании в коде этот самый код становится непонятен даже создателю. При годном использовании может СИЛЬНО облегчить код и исключить использование более сложных конструкций. Собственно срач подразумевает первый случай использования.

«

Как пользоваться оператором GOTO? Подскажите, пожалуйста. А то друг подарил книгу — самоучитель С++, а страница с goto — вырвана.

»

Вы удивитесь, но первый троллинг GOTO возник ещё в 1968 году, когда Эдсгер Дейкстра написал высер «Go To Statement Considered Harmful» («Оператор GOTO считается опасным»). Прочесть его крайне рекомендуется — там тонны лулзов (несмотря на то, что переводил явно надмозг). Чего только стоит первое же предложение: «За многие годы я утвердился во мнении о том, что квалификация программистов — функция, обратно зависящая от частоты появления операторов go to в их программах».

Еще более доставляет тот факт, что фраза «Considered Harmful» сама по себе стала мемом. Так, ответ на статью Дейкстры назывался «’GOTO Considered Harmful’ Considered Harmful», ответ на неё назывался «’„GOTO Considered Harmful“ Considered Harmful’ Considered Harmful?» и так далее (всего 78 (!) статей с данной фразой).

Вообще, в двух словах критика GOTO выражается очень просто: «Хуй поймешь, в каком порядке выполняется этот код». Отладка кода, щедро усыпанного GOTO, напоминает распутывание клубка ниток, причем состоящего из разных кусочков ниток, которые надо собрать в определенном порядке. Кстати, согласно этому оператору, данную статью следует читать начиная с первого раздела, а потом перейти к шестому, и если вы это читаете, не прочтя шестой, то вы ламер, лол.(пруф линк)(факты=}лузлы)goto…


Кстати, если хотите на своем опыте понять проблему GOTO, попробуйте добавить в эту статью новый раздел, сохранив корректность всех GOTO-ссылок.

Так вот, упомянутый во втором разделе язык Basic подвергался отдельной критике Дейкстры: «Студентов, ранее изучавших Бейсик, практически невозможно обучить хорошему программированию. Как потенциальные программисты они подверглись необратимой умственной деградации». А ещё он назвал копирования отечественными компостроителями архитектуры IBM «Величайшей диверсией Запада против СССР», и, ЧСХ, отчасти оказался прав!

Вернемся к GOTO. Любопытный факт: несмотря на массовую нелюбовь, он есть в большинстве современных языков программирования. И выстрелить себе в ногу с его помощью могут многие, под здоровый смех более просветленных быдлокодеров. Но, что интересно, оператор GOTO имеется в исходниках весьма популярных программ, вроде ядра линукса или гугловского «Андроида», более того, сам Линус одобряет использование GOTO в языках программирования не таких дурацких, как Pascal. Почему? Потому что существуют редкие случаи, когда его использование несколько упрощает код. Другое дело, что использование его вне этих случаев немедленно приводит к полному пиздецу. Впрочем, случаи этого в последнее время не так уж часты — вероятно, по той причине, что многие ньюфаги, не знавшие раннего Бейсика, просто не знают о том, что такой оператор существует (для PHP-быдла он, как правило, и не нужен, потому что там редко встретишь сколь-либо сложные алгоритмы).

Все это, однако, не мешает периодически выходить новым статьям, доказывающим или опровергающим то, о чем Дейкстра и его оппоненты написали полвека назад. Под любой такой статьей немедленно начинается спор между GOTO-фагами и GOTO-ненавистниками. Вторые обычно аргументируют «GOTO нельзя, потому что GOTO нельзя, и вообще можно ту же задачу решить без него», а первые осторожно замечают, что иногда это все-таки можно (если не прыгать вверх по программе), к тому же в некоторых языках не хватает адекватных заменителей, а на этих языках по-прежнему пишется код. Например, выход из многовложенных циклов на Си — можно извращаться с return-ами или делать флаги выхода, но, бля… А можно и не извращаться: по правилам структурного программирования break и вложенные циклы строго запрещены, потому что это сводит читаемость кода (и, как следствие, его работоспособность) к нулю. Про бесконечный цикл while(true) вообще молчим. Правильный вариант — использование функций, возвращающих false в случае, если нужно срочно выйти из цикла. Немного соображающий в кодинге тролль может вызвать немало ненависти, выступая за ту или другую сторону.

Но надо сказать, что современные языки с GOTO все-таки стараются ограничить его применение — так, обычно нельзя так просто прыгать между функциями. Так что GOTO уже не тот, что был раньше. Но мы помним!

[править] 40 На самом деле

На самом деле, GOTO — это единственный оператор, который используется для управления выполнением кода этими вашими центральными процессорами. Для всякого ЦП есть в сущности два способа управлять кодом: условный оператор (если да, то выполнить следующую команду, иначе выполнить послеследующую) и перейти на другой участок кода (GOTO). Всё. А что вы хотели, машина Поста, десу! И все эти ваши процедурное программирование, функциональное программирование, ООП, ААП, замыкания, циклы и прочая синтаксическая ересь — после компиляции превращается в сотни, тысячи, миллионы GOTO (а точнее, в JMP, JE, JLE, JGE, JNE, etc.), я гарантирую это! Несогласные goto 30

Последствия использования

Оператор GOTO — древнейший оператор. Используется он для безусловного перехода из одной точки кода в другую. До появления процедур и функций был по сути единственным способом запустить один и тот же код несколько раз. В x86-ом ассемблере этому оператору соответствует JMP.

Люди, начинавшие программировать на раннем Basic’е, безусловно помнят, что каждая строчка на нем нумеровалась (10, 20 и т. д.). Соответственно, написав GOTO 10, можно было скакнуть в самое начало программы. Или ещё куда-нибудь. Многие ранние версии Бейсика позволяли вычисляемый GOTO (хоть от функции RND или INPUT), а иногда в таком лапидарном виде:10 RUN. В более современных языках появились символьные метки, так что стало возможным писать goto nahuy; или ещё что-то в этом роде.

lurkmore.to

Глава 3 — 3.8. Инструкция goto и метки

Глава 3 — 3.8. Инструкция goto и метки

3.8. Инструкция goto и метки

В Си имеется порицаемая многими инструкция goto и метки для перехода на них. Строго говоря, в этой инструкции нет никакой необходимости, и на практике почти всегда легко без нее обойтись. До сих пор в нашей книге мы не использовали goto.

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


    for (...)
        for (...) {
            ...
            if (disaster)    /* если возникла проблема */
                goto error;  /* уйти по ошибке */
            ...
        }
    ...
error:                       /* обработка ошибки */
    ликвидировать проблему

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

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

В качестве еще одного примера рассмотрим такую задачу: определить, есть ли в массивах a и b совпадающие элементы. Один из возможных вариантов ее реализации имеет следующий вид:


    for (i = 0; i < n; i++)
        for (j = 0; j < m; j++)
            if (a[i] == b[j])
                goto found;
    /* нет одинаковых элементов */
    ...
found:
    /* обнаружено совпадение: a[i] == b [j] */
    ...

Программу нахождения совпадающих элементов можно написать и без goto, правда, заплатив за это дополнительными проверками и еще одной переменной:


    found = 0;
    for (i = 0; i < n && !found; i++)
        for (j = 0; j < m && !found; j++)
            if (a[i] == b[j])
                found = 1;
    if (found)
        /* обнаружено совпадение: a[i-1] == b [j-1] */
        ...
    else
        /* нет одинаковых элементов */
        ...

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



Сайт управляется системой uCoz

netlib.narod.ru

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

См. также

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

Примечания

Ссылки

dic.academic.ru

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *