Заборонений плід GOTO солодкий!
Невеликий історичний екскурс
Тим, хто і без мене чудово знає, що таке комбінаційна схема, схема з пам'яттю, і як із цього виріс асемблер – можна сміливо перескакувати далі – висновку.
А все починалося з комбінаційних схем

Іншими словами, Вам потрібно було зробити пристрій, який за значенням змінної (змінних) знаходив значення функції.
Для вирішення цієї складної задачі будувався послідовний алгоритм для виконання арифметичних операцій (у разі заданої точності обчислень у такому алгоритмі кожну арифметичну дію можна виконувати за один такт).
Маючи алгоритм, нескладно побудувати комбінаційну схему - схему, яка миттєво (з точністю до спрацьовування логічних пристроїв та часу поширення сигналів) на виході давала відповідь. Питання – тут потрібні якісь переходи? Ні, їх тут просто немає. Є послідовний перебіг дій. Всі ці дії можна реалізувати зрештою за один такт (не заперечую, це буде дуже і дуже громіздко, але задавшись розрядністю всіх даних, таку схему Вам побудує будь-який студент – і тим більше синтезатор для VHDL або Verilog).
Але потім втрутилися схеми із пам'яттю

Поява таких елементів пам'яті дозволило зробити революційний стрибок вперед від жорстких пристроїв до мікропрограмних автоматів. Спрощено кажучи, мікропрограмні автомати мають пам'ять команд. Є окремий пристрій, який реалізує поточну мікропрограму (додавання, віднімання або ще чогось). А ось вибором "поточної" мікропрограми займається окремий пристрій - нехай це буде "пристрій вибірки".
Чи можна без них обійтись? Та ніяк! Якщо не використовувати переходи, то повернемося до комбінаційної схеми без пам'яті.
У результаті ми прийшли до асемблера
Апофеозом таких обчислювальних пристроїв стали мікро-, просто- та супер-комп'ютери. Всі вони в основі мають мову кодів, що досить легко перетворюється на Асемблер з приблизно збігається набором команд. Розглянемо якийсь усереднений асемблер мікроконтролерів (я знайомий з асемблером для ATmeg-і, PIC та AT90). Як у нього побудовано роботу переходів?
З усією відповідальністю заявляю - без операцій переходу в асемблері обійтися неможливо! Будь-яка програма на асемблері просто таки рясніє ними! Втім, тут зі мною ніхто сперечатися, я гадаю, не буде.
Який підсумок можна підбити? На рівні мікропроцесора операції переходу використовуються дуже активно. Реальну програму, що їх не використовує, написати майже неможливо (можливо, її можна зробити, але це буде супер-мега-збочення і точно вже не реальна програма!). Із цим теж сперечатися ніхто не буде.
Але чому ж тоді в мовах вищого рівня – сконцентруємось на С для мікроконтролерів – оператор goto раптом впав у немилість.
Трохи про алгоритми

Тут A, B, C, D, E - це деякіоперації, а не виклик функції! Цілком можливо, що вони використовують масу локальних змінних. І цілком можливо, що вони змінюють їхній стан. Т. е. в даному випадку не йдеться про виклик функцій - деякі дії, не будемо деталізувати.
Ось як це виглядає з goto:
Дуже лаконічно та читабельно. Але не можна! Спробуємо без goto:
Ви щось зрозуміли з логіки роботи другого лістингу. Порівняємо обидва лістинги:
- На перший лістинг я витратив раз на 5 менше часу, ніж на другий.
- Лістинг з goto коротший як мінімум у 2 рази.
- Лістинг з goto зрозуміє будь-яка людина з мінімальною підготовкою в С. Другий же я постарався зробити максимально доступним і очевидним - і все одно, в нього треба довго вникати.
- Скільки часу піде на налагодження першого варіанта і скільки на налагодження другого?
- І взагалі, якщо вважати намальований алгоритм постановкою завдання, перший листинг правильний на 100%. Про другий я досі не дуже впевнений… хоча б у черговості перевірки умов та прапорів.
- Порівняйте асемблерний код першого і другого лістингу.
Ще мені пропонували цей алгоритм реалізувати приблизно так:
Ну та гаразд, у житті такі алгоритми майже не зустрічаються. Краще поговоримо про життя.
goto у реальних програмах
Я за свій більш ніж 20-річний стаж пройшов кілька апаратних платформ і з десяток мов програмування, брав участь у написанні великого програмного продукту ActiveHDL, робив комерційну базу даних та багато невеликих програм для налагодження обладнання, що використовується в Олімпійськихіграх, а також робив пристрої для цієї Олімпіади (вже кілька Олімпіад, якщо бути точним). Коротше, щось я в програмуванні нишпорю. А, так, забув - я закінчив з почесним дипломом ХНУРЕ - тобто, в теорії я теж січу.
Тому мої подальші міркування та ситуації… скажімо так, я маю моральне право на них.
Неявне використання goto
У мові С є багато операторів, які насправді є банальним goto – умовним чи безумовним. Це всі види циклів for (…), while (…), do while (…). Це аналіз числових змінних switch (…). Це самі оператори переривання/переходу в циклах break і continue. Зрештою, Це виклики функцій funct() і вихід з них return.
Ці goto вважаються "легальними" - чим же нелегальний сам goto?
У чому звинувачують goto
Звинувачують його в тому, що код стає нечитабельним, погано оптимізується і можуть з'явитися помилки. Це про практичні мінуси. А теоретичні – це просто погано та неписьменно, і все тут!
Вам не здається, що ніж – це дуже небезпечна річ? Але чомусь на кухні ми ним користуємось. А 220 вольт – жах як небезпечно! Але якщо користуватися розумом – жити можна.
Теж саме і goto. Користуватися ним треба розумно – і тоді код працюватиме коректно.
А про теоретичні докази – це, вибачте мені, суперечка про смаки. Ви користуєтесь Угорською нотацією? Я – ні, терпіти її не можу! Але ж я не кажу, що вона погана через це! Особисто я вважаю, що змінна має нести смислове навантаження – навіщо вона створена. Але я не заборонятиму користуватися цим способом іменування іншим людям!
Або є естети, які вважають, що писати a = ++i неграмотно, треба писати i = i + 1; a = i. І що тепер заборонити і це теж?
Обробка помилок
Візьмемо обробку вхідних пакетів із якогось зовнішнього пристрою:
Ми отримали назву пакету. Проаналізували. Ага, пакет 'A' — отже, нам треба 10 разів зробити щось. Ми не забуваємо контролювати час роботи цієї ділянки — а раптом друга сторона зависла? Ага, таки зависла – спрацювала умова timeout – тоді виходимо назовні – з циклу, із switch.
Те саме можна зробити і за допомогою усіляких прапорів — але працювати це буде повільніше, плюс до всього займе такий дефіцитний осередок пам'яті. Воно того варте?
Це випадок простий. Адже receive_byte() теж може бути макрофункцією з обробкою таймаутів. І там теж будуть такі ось різкі виходи.
Це саме той випадок, де я активно використовую goto. Це мені дозволило не потрапляти в «зависання» у разі проблем із зовнішніми пристроями, UART, USB тощо.
Вихід із вкладеного циклу назовні
Подивіться на програму нижче:
Що відбувається – зрозуміло? Є вкладений цикл. Якщо настала якась умова – залишаємо всі подальші обробки.
Цей код із прапорами виглядає інакше:
Що сталося у цьому випадку? На кожній ітерації ми тепер перевіряємо прапор. Не забуваємо його перевіряти й надалі. Це дрібниці, якщо ітерацій небагато і йдеться про «безрозмірну» пам'ять у PC. А коли програма написана для мікроконтролера – це вже стає суттєво.
До речі, у зв'язку з цим у деяких мовах (якщо не помиляюся, Java) є можливість вийти з циклу за міткою виду break Leave. Той же goto, між іншим!
Такий самий приклад я можу навести і з обробкою в switch (…) < case …>. З цим я часто стикаюся при обробці вхідних пакетів неоднакової структури.
Автоматичне створення коду
Чи знайомі Ви з автоматнимпрограмуванням? Чи будь-яким іншим автоматизованим створенням коду? Скажімо, творці лексичних обробників (без використання громіздкого boost:spirit). Всі ці програми створюють код, який можна використовувати як "чорну скриньку" - Вам не важливо, що там усередині; Вам важливо, що він робить. А всередині там goto використовується дуже часто.
Вихід в одному місці
На С іноді доводиться писати щось на кшталт:
Цей код набагато акуратніше виглядатиме так:
Ідея зрозуміла? Іноді треба при виході зробити щось. Іноді багато чого треба зробити. І тоді тут чудово допомагає goto. Такі приклади у мене також є. Начебто все перерахував, тепер можна підвести…
Цемоя думка! І вона справедлива для мене. Може – і для Вас, але я не Вас змушуватиму їй слідувати!
Так ось, для мене очевидно, що goto допомагає оптимальніше та якісніше вирішити деякі проблеми. А буває і навпаки - goto може породити масу проблем.
Плюси використання goto:
- найоптимальніший (з т. зр. лістингу та результуючого коду) вихід із кількох вкладених циклів та switch… case
- Сі: найбільш економічний (за лістингом та результуючим кодом) спосіб обробки помилок
- в окремо взятих випадкахнайоптимальніша побудова алгоритму
- економить пам'ять і такти приакуратномувикористанні, що іноді буває першорядно важливим
- незвичність коду
- порушення ходу проходження читання листингу зверху вниз і стандартизованого обходу блоків у коді (у сенсі, що можливий перехід у центр блоку, а також вихід з його)
- ускладнення компілятору (а іноді й неможливість) процесу оптимізації коду
- підвищення ймовірності створення трудноуловимых помилок у коді
Ще раз звертаю увагу:я не закликаю тулити goto всюди! АЛЕ в деяких випадках він дозволяє реалізувати алгоритм куди ефективніше за решту коштів.