Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Прочее

Некошерный «goto»

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

Одному Уважаемому Человеку взбрело в голову, что программа должна исполняться так как она читается, а goto - это зло. Этот частный случай, удобный при решении определенного класса задач, был затем доведен подпевалами до абсурда, что привело к физическому уничтожению несчастного оператора в ряде языков. Инквизиция жгла старые книги, пела дифирамбы кастратам, а в школах запретили даже упоминание обрезанного рудимента (ставшего атавизмом).

Некошерный «goto»
            В предыдущей статье мы предложили такой синтаксис, который не оставляет места оператору «goto». С какой стороны ни взгляни на гипотетические участки кода программы — везде предложены конструкции, которые и лаконичнее, и понятнее, нежели эквивалентные варианты с «goto». Циклы, условный оператор, переключатель не нуждаются в «goto». Как видим, причина «убийства goto» не религиозная, а чисто практическая: он совершенно не нужен.

            Остаётся последний аргумент: текст программы может создаваться не человеком, а другой программой; в этом случае он бы пригодился.

            Но на это тоже есть возражения:
  • Во-первых, далеко не факт, что разрабатываемый нами язык будет досточно хорош как язык низкого уровня, своего рода ассемблер. Язык Си для этих целей, наверно, более походящ. Компилятор Си есть фактически для всех платформ.
  • Во-вторых, разработанные нами конструкции позволяют обойтись без «goto» не только программисту, но программе, генерирующей код.
  • Ну и в третьих, включить его в язык никогда не поздно, всегда успеется. А вот исключить его будет гораздо сложнее.

Не верьте данайцам, «goto» приносящим

            Сторонники «goto» могут оправдывать право на существование этого оператора сложностью поставленной задачи. Например, в статье «Запретный плод GOTO сладок!» приводится достаточно сложная, на первый взгляд, блок-схема:
Якобы сложный алгоритм, требующий «goto»
            Автор указанной статьи приводит реализация алгоритма этой блок схемы с использованием «goto», которая выглядит так:
if (a)
{
    A;
    goto L3;
}
L1:
if (b)
{
L2:
    B;
L3:
    C;
    goto L1;
}
else if (!c)
{
    D;
    goto L2;
}
E;
Без «goto» она значительно длиннее:
char bf1, bf2, bf3;

if (a)
{
    A;
    bf1 = 1;
}
else
    bf1 = 0;

bf2 = 0;
do
{
    do
    {
        if (bf3 || b)
            bf3 = 1;
        else
            bf3 = 0;
        if (bf3 || bf2)
            B;
        if (bf3 || bf1 || bf2)
        {
            C;
            bf1 = 0;
            bf2 = 1;
        }
        if (!bf3)
        {
            if (!c)
            {
                D;
                bf3 = 1;
            }
            else
            {
                bf3 = 0;
                bf2 = 0;
            }
        }
    }
    while (bf3);
}
while (bf2);

E; 
            Из этого вроде бы надо сделать вывод, что «goto» в каких-то случаях помогает написать код короче и понятнее. Не будем торопиться делать такие выводы. Дело в том, что приведённая блок-схема демонстрирует «творческий беспорядок» в голове её автора. Так блок-схемы писаться не должны. Переделаем её, где-то уповая на интуицию, а где воспользуемся советами Владимира Даниеловича Паронджанова – самого известного специалиста в стране по блок-схемам. Он советует располагать выход из блок-схемы строго под входом. Сделаем перестановки блоков в схеме, ничего не меняя – просто располагаем блоки по-другому.
Алгоритм стал понятнее
            Полученная блок-схема полностью эквивалентна исходной. Теперь рассмотрим блок-схему повнимательнее.

            Когда условие «a» истинно, маршрут выполнения программы предусматривает последовательное выполнение блоков «A» и «C», а после чего маршрут приводит к условию «b». Когда «a» ложно, к условию «b» попадают сразу.

            Когда условие «b» истинно, маршрут выполнения программы предусматривает последовательное выполнение блоков «B» и «C», а после чего маршрут снова приводит к условию «b». Когда «b» ложно, попадают к условию «c».

            Когда условие «c» ложно, маршрут выполнения программы предусматривает последовательное выполнение блоков «D», «B» и «C», а после чего маршрут опять приводит к условию «b». Когда «c» истинно, выполняется блок «E» и на этом алгоритм завершается.

            Теперь опять вооружаемся учением В.Паронджанова, автора «Дракона». «Дракон» - это своего рода нормализованные блок-схемы, гарантирующие непересечение линий блок-схем. Снова преобразуем блок-схему, да так, чтобы линии не пересекались:
Алгоритм стал очень простым
            Блок схема стала весьма и весьма простой. Этому алгоритму соответствует такой код на Си:
if (a) { A; C;}
do {
    if (b) { B; C; continue;}
    D; B; C;
} while (!c);
E;
Сравнивая этот код с тем, что предложил автор статьи на «Хабрахабре», можно воскликнуть: «Не верьте данайцам, «goto» приносящим».

            Те преобразования блок-схемы, которые сделаны, чтобы прийти к такому краткому решению, делались больше по наитию. Если же Вам была дана сложная блок-схема, которую предстоит воплотить в код, то лучше преобразовывать её «по науке». В. Паронджанов выполнил строгое математическое доказательство, что любая блок-схема может быть преобразована в эквивалентную, которая исключает пересечение маршрутных линий. Это очень важный научный вывод. Непересечение маршрутных линий блок-схемы равнозначно тому, что в программе не будет операторов «goto».

            Вывод: какие бы доказательства не приводили в защиту «goto», всегда найдётся способ обойтись без него и написать программу короче и не менее наглядно. При наличии удобных конструкций в языке, конечно.

Что ещё почитать на эту тему

Последняя правка: 2016-07-13    17:20

ОценитеОценки посетителей
   ██████████████ 3 (33.3%)
   ██████████████ 3 (33.3%)
   ▌ 0
   ██████████████ 3 (33.3%)

Отзывы

     2013/05/15 19:11, Vovanium

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

Не особенно то опытные любители попадались, видно. Претендентом на goto является любая структура управления, не раскладываемая на элементарные ветвления и циклы без повторов.

Мне вот периодически попадается такого рода:
"если a то (A, если b то B иначе C) иначе C"
Где A, B, C — некоторые последовательности действий, возможно весьма длинные, возможно использующие общие переменные и т. п.
Здесь C должно выполняться, если не выполнилось условие a ИЛИ вложенное условие b. Последовательность A не даёт использовать "если a или b то ... иначе C".
Каждый раз встаёт дилемма:
или писать код с повтором;
или использовать третье условие
или факторизовать участок C в процедуру,
или использовать goto.
Подчас оказывается, что goto выигрывает у каждого из вариантов по размеру кода, скорости, читабельности, а то и по всем параметрам сразу.
Подобный же пример — множество альтернативных веток (if или switch/case не важно), и каждая ветка может завершиться условно успешно, или неуспешно, и в зависимости от этого надо завершить процедуру тем или иным методом.
На этот случай во многих языках есть механизм исключений, который по сути — тот же завуалированный goto, да ещё и гораздо менее предсказуемый, примерно как longjmp.

     2013/05/16 15:28, Автор сайта

А как этот код выиграет от «goto»?
(если а
    А(...)
    (если b
         В(...)
    иначе
         C(...))
иначе
    C(...))
Что плохого в том, чтобы сделать C(...) процедурой? Два повторяющихся участка кода лучше заменить процедурой; исправления, вносимые в С(...), будут действовать в двух местах. Иначе придётся в два участка вносить одинаковые исправления. При невнимательности исправления могут сделать код неидентичным.

     2014/01/16 09:56, Pensulo

На VisualBasic лаконичнее будет решить задачку таким образом:
If a Then
  A()
ElseIf b Then
  B()
Else
  C()
End If
Где a, b, c - логические выражения
A(), B(), C() - выполняемые операции (частным случаем которых является вызов функций)

     2014/04/29 02:36, Utkin

Что плохого в том, чтобы сделать C(...) процедурой?

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

     2014/04/30 12:49, Автор сайта

Если участки идентичны, то они контекст используют одинаково. Связывание процедуры с внешним окружением через параметры – это, конечно, накладные расходы. Правда, в C++ есть inline-процедуры: в месте вызова будет делаться не вызов процедуры, а подстановка её кода. В этом случае накладные расходы исключены. Результат же можно не возвращать, а изменять через указатель, переданный в качестве параметра, например:
strcpy (char*  куда, const char* откуда);    // копирование строк

     2014/06/17 04:36, utkin

Результат же можно не возвращать, а изменять через указатель, переданный в качестве параметра, например:

Современная тенденция - отказ от указателей и это правильно. Указатели трудно отслеживать и медленно и сложно. В общем не эффективно. А программисту доверять указатели нельзя - он плохо представляет механизмы работы всей системы. Это приводит к утечкам памяти, зависаниям, уязвимостям в безопасности системы. В общем явно так обычно не говорят, но сейчас указатели это зло. Есть более выскоуровневые механизмы - динамические массивы и списки.

В этом случае накладные расходы исключены.

Эти расходы вылазиют в отладке программы в виде значительных неконтролируемых временных затрат.

     2014/06/17 16:35, Автор сайта

Речь шла о том, как обойтись без «goto». Его применяют в том случае, когда исполнитель плохо представляет, как сделать хороший алгоритм. В этом случае можно дать костыли, менее стрёмные, чем «goto». Хотя, по-хорошему, надо заново обдумать алгоритм.

Есть более выскоуровневые механизмы - динамические массивы и списки.

Есть решения ещё лучше – использование чистых функций, например. Всё остальное – от лукавого. Динамические массивы – о них тоже пока громко не говорят, что это есть зло. Но скоро скажут.

     2014/06/30 12:19, utkin

Его применяют в том случае, когда исполнитель плохо представляет, как сделать хороший алгоритм.

А что значит «хороший» алгоритм? Чем измеряется хорошесть goto? Не забывайте, что в ассемблере целое семейство goto, и программы написаны и работают. В конце концов, компилятор также переводит программу без goto в программу с многочисленными вариациями условных переходов, а часто и с безусловными переходами.

Динамические массивы – о них тоже пока громко не говорят, что это есть зло.

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

     2014/06/30 16:15, Автор сайта

А что значит «хороший» алгоритм?

Очевидный, прозрачный, не вводящий в заблуждение.

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

Ну так эти компиляторы ещё и в двоичный код всё преобразуют. Но это не повод писать в двоичных кодах: прогресс не стоял на месте.

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

Любой объект, созданный в динамической памяти (в том числе и динамические массивы), доступен только через его адрес, который в C++ имеет материальное воплощение в виде указателя или ссылки. В других языках это просто спрятано за кулисы и создаётся впечатление «порядочности».
Я на тему памяти напишу через некоторое время.

     2014/06/30 17:43, utkin

Очевидный, прозрачный, не вводящий в заблуждение.

Это очень зыбкая тема. Даже перекликаясь с соседними темами - для меня а++ не очевидно, не прозрачно и вводит в заблуждение (в сочетаниях а++, ++а, а=а+1). Однако большинство языков программирования используют такую (или аналогичную) запись. Если же просто докапываться - бред и сложночитаемую программу можно написать и структурировано (используя неочевидные реализации алгоритма). В тоже время можно использовать goto с умом, организовав переходы строго логично, если выработать методику и приложить некоторые усилия. Единственный постулат уничтожающий goto это только то, что любую программу можно написать без использования goto. Больше объективных причин для изгнания этого оператора нет.

В других языках это просто спрятано за кулисы и создаётся впечатление «порядочности».

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

     2015/10/25 05:35, rst256

Кажется заместо «goto» нам предлагают копипастить куски кода, в таком случае я тоже отвечу копипастом.

Стр.457. Моя позиция по отношению к концепции структурного программирования не помещается в Вашу классификацию. Возможно, кто-то еще придерживается подобной точки зрения, но я таких не знаю. Я считаю эту концепцию ошибочной и вредной. Оператор «goto» совершенно безобидный оператор по сравнению с циклом типа «while» и указателями, которые представляют серьезную беду для программирования. Положительным было лишь постановка задачи построения хороших программ и толчок к исследованиям в этом направлении. Структурное программирование было внедрено как религия и до сих работает как религия. Неудавшаяся попытка устранения оператора «goto» из языков программирования, вакханалия структурного программирования долгие годы – это процесс в ложном направлении.

Источник: http://drakon.su/_media/kritika/kritika_01.rec.pdf

     2015/10/25 18:31, Автор сайта

Я задавал вопрос Андрею Карпову, как влияет «goto» на работу анализатора кода PVS-Studio. Он ответил: «Очень плохо». Обещал статью на эту тему, но пока её нет. Т.е. отрицательное мнение об этом операторе принадлежит не теоретику программирования, а практику. Да и зачем в кусок кода, который должен выполняться многократно, переходить по «goto»? Этот код надо сделать inline-функцией. Компилятор сделает либо подстановку при оптимизации на скорость или вызов при оптимизации на размер.

«Дикие» указатели – действительно плохая практика. И обычных циклов лучше избегать, заменяя их, где получается, циклом «for each». Но это не значит, что «goto» – это лучше, чем указатели и циклы. Во множестве вполне удачных языков «goto» отсутствует, и я не встречал жалоб разработчиков, что им его не хватает.

     2015/11/13 01:46, rst256

Я задавал вопрос Андрею Карпову, как влияет «goto» на работу анализатора кода PVS-Studio. Он ответил: «Очень плохо».

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

     2015/11/13 17:01, Автор сайта

Всё, что можно сделать, применяя «goto», можно сделать и без него, не используя дополнительные переменные, не дублируя код. Появление же инструкций «jmp» в исполняемом коде неизбежно. Но это уже неважно, потому что он появится поле компиляции программы, правильно написанной благодаря отсутствию «goto».

     2016/03/25 04:58, rst256

такой код на Си: while (1) { ...

А это нормальный анализатор не смутит? Или он такой умный стал что определяет такое. Тогда какие проблемы у него с goto?

В любом случае таки да тогда нам хватит покаона (while(1) => while alone) но чтоб отдельной конструкцией pokaone { ... }

     2016/04/04 13:33, Автор сайта

Цикл имеет выход по условию. Похоже на for (; !c;), только проверка не в начале, а в средине.

     2016/04/11 16:54, rst256

Нет, не только выход по условию, но и продолжение.

while 1 {
...
if some_condition_break1 then ... break end
...
if some_condition_break1 then ... break end
...
if some_condition_continue1 then ... continue end
...
}

причем "then ... " это еще и некоторый набор действий перед выходом/продолжением цикла.

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

     2016/04/11 18:03, Автор сайта

Код, который нужно выполнить при выходе, можно оформить обычными конструкциями:
(if условие
код при выходе
break)
То же самое – для «continue». Если выходов несколько, то было бы желательно код не дублировать. Допустим, это можно сделать так:
(while условие
(if условие 1
break)
(if условие 2
break)
on break
код при выходе)
Но это утяжелит язык. Вы, полагаю, хотите решить все проблемы простым наличием «goto»? «goto» облегчает язык?

     2016/07/13 09:14, rst256

«goto» облегчает язык?

Несомненно, все приемы структурного программирования можно представить через goto: он универсальный оператор, а структурный подход – это ограниченный набор из нескольких конструкций. Это можно сравнить с литейной формой и резцом, форма выигрывает против резца, но только пока она совпадает с тем, что вам нужно получить...
Так вы точно уверены, что резец вам таки никогда не понадобится, и набора из десятка формочек хватит на все случаи жизни?

     2016/07/13 09:19, rst256

Просто добавим еще кода...
(while условие
(if условие 1
break 2)
(if условие 2
break1)
(if условие 3
...
(if условие 4
break 1)
...
)
on break1
код при выходе 1
on break 2
код при выходе 2
)
И структурное программирование уже не тянет...

     2016/07/13 09:39, rst256

Вывод: какие бы доказательства не приводили в защиту «goto», всегда найдётся способ обойтись без него и написать программу короче и не менее наглядно. При наличии удобных конструкций в языке, конечно.

Например #define, да? А что тогда мешает подставить туда goto? Так хоть багов меньше будет, например в одном из блоков присутствует "int i;" будет баг, а как рекурсию такой подход осилит? Рекурсию через goto иногда оптимальнее делать.
Системный язык, ориентированный на быстродействие, должен иметь оператор goto!

     2016/07/13 18:39, Автор сайта

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

«goto» не выполняет операций над данными. Этот оператор лишь управляет ходом вычислений. Какие алгоритмы нуждаются именно в «goto»? Можно привести пример сложностей, которые возникнут при реализации алгоритма, если «goto» отсутствует? С алгоритмической сложностью можно справляться либо декомпозицией, либо инструментами типа «Дракона» (своего рода «нормализация» блок-схем). Ограниченность набора кубиков, из которых складывается алгоритм, не является проблемой, если обеспечивается достаточность. А если это способствует верификации алгоритма инструментами типа PVS-Studio, то такую ограниченность можно даже приветствовать.

Такой код лучше:
(while условие А
(if условие 1
break)
(while условие Б
(if условие 2
break 2)
(if условие 3
break)
...
on break
код при выходе из внутреннего цикла
)
on break
код при выходе из объемлющего цикла
)
Тогда всё выглядит логичнее.

Системный язык, ориентированный на быстродействие, должен иметь оператор goto!

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

В. Паронджанов выполнил строгое математическое доказательство, что любая блок-схема может быть преобразована в эквивалентную, которая исключает пересечение маршрутных линий. Это очень важный научный вывод. Непересечение маршрутных линий блок-схемы равнозначно тому, что в программе не будет операторов «goto».

     2016/09/08 07:46, rst256

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

Ага вот только это задолго до него было доказано Тьюрингом.

Можно привести пример сложностей, которые возникнут при реализации алгоритма, если «goto» отсутствует?

Сложностей или алгоритма? Если сложностей, то она всегда одна: «goto» отсутствует! Если алгоритмов, то это в основном метапрограмирование:цикл с условием в середине, генерация кода самим Драконом. Знай мы заведомо требуемый алгоритм, смогли бы наверное переписать его без goto, но вот беда: мы его в данном случае НЕ ЗНАЕМ, тот же цикл с условием в середине:
...код...
нач_цикла1:
...код...
(если условие goto кон_цикла1)
...код...
goto нач_цикла1;
кон_цикла1:
...код...
Конечно если вы предпочитаете хак "while (1)..." можно и его использовать, но как вы тогда реализуете к примеру break 2? И еще такой момент: может быть В. Паронджанов напротив очень сильно любит goto, и не желает им ни с кем делиться? Ведь добавь он его в свой язык – ему пришлось бы учитывать возможность конфликта меток пользователя и генерируемых самим Драконом.

Написать отзыв

Написать автору можно на электронную почту mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Каким должен быть язык программирования?

Анализ и критика

Устарел ли текст как форма представления программы

Русский язык и программирование

Многоязыковое программирование

Синтаксис языков программирования

Синтаксический сахар

Некоторые «вкусности» Алгол-68

«Двухмерный» синтаксис Python

Почему языки с синтаксисом Си популярнее языков с синтаксисом Паскаля?

Должна ли программа быть удобочитаемой?

Стиль языка программирования

Тексто-графическое представление программы

●  Разделители

●  Строки программы

●  Слева направо или справа налево?

Комментарии

●  Длинные комментарии

●  Короткие комментарии

●  Комментарии автоматической генерации документации

●  Нерабочий код

Нужны ли беззнаковые целые?

Шестнадцатиричные и двоичные константы

Условные операторы

Переключатель

Циклы

●  Продолжение цикла и выход из него

Некошерный «goto»

Операции присвоения и проверки на равенство. Возможно ли однаковое обозначение?

Так ли нужны операции «&&», «||» и «^^»?

Постфиксные инкремент и декремент

Почему в PHP для конкатенации строк используется «.»?

Указатели и ссылки в C++

Использование памяти

Почему динамическое распределение памяти – это плохо

Как обеспечить возврат функциями объектов переменной длины?

●  Типы переменного размера (dynamically sized types, DST) в языке Rust

●  Массивы переменной длины в C/C++

●  Размещение объектов в стеке, традиционный подход

●  Размещение объектов переменной длины с использованием множества стеков

●  Размещение объектов переменной длины с использованием двух стеков

●  Реализация двухстековой модели размещения данных

●  Двухстековая модель: тесты на скорость

●  Размещение объектов переменной длины с использованием одного стека

Можно ли забыть о «куче», если объекты переменной длины хранить в стеке

Безопасность и размещение объектов переменной длины в стеке

Массивы, структуры, типы, классы переменной длины

О хранении данных в стеке, вместо заключения

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

Компьютерный юмор

Прочее

Последние комментарии

2018/04/16 15:09, Олег
Русский язык и программирование

2018/04/02 22:42, rst256
Программирование без программистов — это медицина без врачей

2018/03/25 21:14, Денис Будяк
Энтузиасты-разработчики компиляторов и их проекты

2018/03/21 23:37, Marat
Почему обречён язык Форт

2018/03/10 20:05, Comdiv
«Двухмерный» синтаксис Python

2018/02/24 14:51, Эникейщик
Русской операционной системой должна стать ReactOS

2017/12/12 13:32, Comdiv
Отечественные разработки

2017/11/05 17:26, rst256
Электроника без электронщиков