Некошерним «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», завжди знайдеться спосіб обійтися без нього і написати програму коротше і не менш наочно. При наявності зручних конструкцій в мові, звичайно.
Що ще почитати на цю тему
Опубліковано: 2012.09.25, остання правка: 2019.01.27 20:17
Відгуки
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 9:56, Pensulo #
На VisualBasic лаконічніше буде вирішити задачку таким чином: If a ThenA ()
ElseIf b Then
B ()
Else
C ()
End If Де a, b, c - логічні вираження
A (), B (), C () - виконуються операції (окремим випадком яких є виклик функцій)
2014/04/29 2:36, Utkin #
Що поганого в тому, щоб зробити C (...) процедурою?
Те, що С може використовувати поточний контекст, тобто місцеві локальні змінні, які доведеться передавати потім в процедуру і повертати їх якось кілька штук. Як пам'ятається, найбільш стале уявлення про функції передбачає завжди один, єдиний результат.2014/04/30 12:49, Автор сайту #
Якщо ділянки ідентичні, то вони контекст використовують однаково. Зв'язування процедури із зовнішнім оточенням через параметри - це, звичайно, накладні витрати. Правда, в C ++ є inline-процедури: в місці виклику буде робитися не виклик процедури, а підстановка її коду. В цьому випадку накладні витрати виключені. Результат же можна не повертати, а змінювати через покажчик, переданий як параметр, наприклад: strcpy (char * куди, const char * звідки); // копіювання рядків2014/06/17 4: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 5: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 1:46, rst256 #
Я задавав питання Андрію Карпову, як впливає «goto» на роботу аналізатора коду PVS-Studio. Він відповів: «Дуже погано».
Тобто в питанні вибору стилю кодування ми вибираємо варіант найбільш зручний аналізатору коду, тому що йому складно розібратися в переходах? Програмісту не складно, компілятору теж, але аналізатор важливіше адже в коді без «goto» починає плутатися програміст, адже то що робилося простий логічною командою тепер потрібно робити через допоплнітеьние змінні, повторні перевірки, дублювання коду і т.д. А «goto» компілятор все одно в код додасть в результаті ...2015/11/13 17:01, Автор сайту #
Все, що можна зробити, застосовуючи «goto», можна зробити і без нього, не використовуючи додаткові змінні, що не дублюючи код. Поява ж інструкцій «jmp» в виконуваному коді неминуче. Але це вже не важливо, тому що він з'явиться поле компіляції програми, правильно написаної завдяки відсутності «goto».2016/03/25 4: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 9:14, rst256 #
«Goto» полегшує мову?
Безсумнівно, всі прийоми структурного програмування можна уявити через «goto»: він універсальний оператор, а структурний підхід - це обмежений набір з декількох конструкцій. Це можна порівняти з ливарної формою і різцем, форма виграє проти різця, але тільки поки вона збігається з тим, що вам потрібно отримати ...Так ви точно впевнені, що різець вам таки ніколи не знадобиться, і набору з десятка форм вистачить на всі випадки життя?
2016/07/13 9: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 7:46, rst256 #
Можна навести приклад складнощів, які виникнуть при реалізації алгоритму, якщо «goto» відсутній?
Складнощів або алгоритму? Якщо складнощів, то вона завжди одна: «goto» відсутня! Якщо алгоритмів, то це в основному метапрограмірованіе: цикл з умовою в середині, генерація коду самим Драконом. Знай ми свідомо необхідний алгоритм, змогли б напевно переписати його без «goto», але от лихо: ми його в даному випадку НЕ ЗНАЄМО, той же цикл з умовою в середині: ... код ...нач_цікла1:
... код ...
(Якщо умова goto кон_цікла1)
... код ...
goto нач_цікла1;
кон_цікла1:
... код ... Звичайно якщо ви віддаєте перевагу хак "while (1) ..." можна і його використовувати, але як ви тоді реалізуєте наприклад break 2? І ще такий момент: може бути В. Паронджанов навпаки дуже сильно любить «goto», і не бажає їм ні з ким ділитися? Адже додай він його в свою мову - йому довелося б враховувати можливість конфлікту міток користувача і генерує сам Драконом.
2019/02/08 10:23 kt #
Хотілося б згадати і про «кошерної» GOTO.Я маю на увазі переходи в програмах на асемблері. Свого часу, по-моєму, в асемблері СМ-4 я побачив таку примочку, як «тимчасові» або «одноразові» мітки. Тобто там можна було використовувати мітки з іменами $ 1 $ 63 в тексті між двома «справжніми» тобто звичайними мітками. Після чергової звичайної мітки знову можна було використовувати мітки, що починаються з $, і тепер це були вже інші місця в тексті.
Оскільки я тоді багато писав на асемблері, і мені набридало вигадувати унікальні назви, я вставив схожий механізм в транслятор RASM. Мітки можна було просто позначати символом @. У транслятора був внутрішній лічильник цих міток. Кожен раз, коли в операндах команди зустрічався @, транслятор додавав до нього 16-ковий значення цього лічильника у вигляді тексту, тобто виходили мітки @ 0000 @ 0001 і так до @FFFF. Коли ж транслятор зустрічав саму чергову мітку (тобто текст @ :), він теж прісобачівал до неї номер і потім збільшував лічильник на 1.
До чого все це я? В результаті на асемблері виходили трохи більше кошерні переходи, оскільки на такі мітки можна було переходити тільки вперед. І якщо дотримуватися правил, що до найближчої мітки @ в тексті інших позначок не повинно бути, асемблерний текст з переходами читався набагато легше. Читаєш і розумієш, що перехід йде вниз по тексту і зазвичай кудись недалеко, тобто ось зараз ця мітка і з'явиться.
Мені здається, що якщо в мові високого рівня мати подібний механізм «одноразових» міток, кошерность GOTO підвищиться. Хоча, звичайно, і помилки легко вносити.
2019/02/08 16:34, Автор сайту #
Ідея, звичайно, оригінальна і дотепна. Я б, напевно, цим із задоволенням користувався. Якщо, звичайно, повернути ті часи, коли трава була зеленішою, а ночами снилися програми на асемблері. Але мови високого рівня без цього обійдуться, так буде краще і програмами, і програмістам. Арсенал різних видів розгалужень в мовах високого рівня не дає шансів «goto» і суворіше дотримується структуру програми.Додати свой відгук
Написати автору можна на електронну пошту mail (аt) compiler.su
А що значить «хороший» алгоритм?Чим вимірюється хорошість «goto»?
Та й навіщо в шматок коду, який повинен виконуватися багаторазово, переходити по «goto»?
Тобто в питанні вибору стилю кодування ми вибираємо варіант найбільш зручний аналізатору коду, тому що йому складно розібратися в переходах?
А це нормальний аналізатор не збентежить?
Тоді які проблеми у нього з «goto»?
Ви, гадаю, хочете вирішити всі проблеми простим наявністю «goto»?
«Goto» полегшує мову?
Так ви точно впевнені, що різець вам таки ніколи не знадобиться, і набору з десятка форм вистачить на всі випадки життя?
Например #define, так?