До яких помилок призводять «goto» заяви? Чи є історично значущі приклади?


103

Я розумію, що зберегти для виривання петель, вкладених у петлі; gotoзаяву оминули і паплюжив як помилку схильного стиль програмування, ніколи не буде використовуватися.

XKCD Alt Text: "Ніл Стівенсон вважає, що мило називати його лейблами" dengo ""
Дивіться оригінал коміксу за адресою: http://xkcd.com/292/

Тому що я це навчився рано; Я насправді не маю уявлення чи досвіду щодо того, до яких типів помилок gotoнасправді призводить. То про що ми тут говоримо:

  • Нестабільність?
  • Неможливий чи нечитабельний код?
  • Вразливості безпеки?
  • Щось ще цілком?

До яких помилок насправді призводять "goto" заяви? Чи є історично значущі приклади?


33
Чи читали ви оригінальну коротку книжку Дікстри на цю тему? (Це питання "Так" / "Ні", і будь-яка відповідь, крім однозначного миттєвого "так" - це "ні".)
Джон Р. Стром

2
Я припускаю, що сигнал тривоги - це те, коли трапляється щось катастрофічне. звідки ти ніколи не повернешся. Як і збою живлення, зникнення пристроїв або файлів. Деякі види "on error goto" ... Але я думаю, що більшість "майже катастрофічних" помилок в даний час може бути вирішено "спроб - catch" конструкцій.
Ленн

13
Ніхто ще не згадав обчислений GOTO. Ще тоді, коли земля була молодою, BASIC мав рядкові номери. Ви можете написати наступне: 100 N = A * 100; GOTO N. Спробуйте це
налагодити

7
@ JohnR.Strohm Я прочитав статті Dijkstra. Він був написаний в 1968 році і дозволяє припустити, що потреба в гото може бути зменшена в мовах, що включають такі розширені функції, як оператори комутації та функції. Він був написаний у відповідь на мови, якими goto був основним методом управління потоком, і його можна було використовувати для переходу до будь-якої точки програми. В той час як мовами, розробленими після написання цього документу, наприклад, C, goto може переходити лише до місць у тій самій рамці стека, і зазвичай використовується лише тоді, коли інші параметри не працюють добре. (продовження ...)
Рей

10
(... продовження) Його моменти були дійсними на той час, але вже не є актуальними, незалежно від того, гото - це гарна ідея чи ні. Якщо ми хочемо стверджувати, що так чи інакше, повинні бути аргументи, що стосуються сучасних мов. До речі, ви читали "Структуроване програмування з goto Statements" Кнута?
Рей

Відповіді:


2

Ось спосіб подивитися на це я ще не бачив в інших відповідях.

Йдеться про сферу застосування. Одним із головних стовпів належної практики програмування є чітке використання вашої сфери. Вам потрібна чітка сфера, тому що вам не вистачає розумової здатності наглядати і розуміти більше, ніж лише кілька логічних кроків. Отже, ви створюєте невеликі блоки (кожен з яких стає "однією річчю"), а потім будуєте разом з тими, щоб створювати більші блоки, які стають однією справою тощо. Це зберігає речі керованими та зрозумілими.

Гото ефективно роздуває сферу вашої логіки для всієї програми. Це обов'язково переможе ваш мозок за будь-які, але найменші програми, що охоплюють лише кілька рядків.

Тож ви вже не зможете сказати, чи помилилися ви більше, бо просто занадто багато, щоб взяти та перевірити свою бідну маленьку голову. Це справжня проблема, помилки - лише ймовірний результат.


Немісцевий гото?
Дедуплікатор

thebrain.mcgill.ca/flash/capsules/experience_jaune03.html << Людський розум може відслідковувати в середньому 7 речей одночасно. Ваше обгрунтування грає це обмеження, оскільки розширення сфери застосування додасть ще багато речей, про які слід слідкувати. Таким чином, типи помилок, які будуть введені, ймовірно, є упущенням і забудькуватістю. Ви також пропонуєте пом'якшувальну стратегію та добру практику в цілому щодо "чіткості вашої сфери". Як такий, я приймаю цю відповідь. Хороша робота Мартін.
Аківа

1
Як це круто ?! Серед понад 200 поданих голосів виграв лише 1!
Мартін Маат

68

Справа не в gotoтому, що сама по собі погана. (Зрештою, кожна інструкція зі стрибків на комп'ютері - це гото.) Проблема полягає в тому, що існує людський стиль програмування, який заздалегідь датував структуроване програмування, що можна було б назвати програмуванням «блок-схеми».

У програмуванні блок-схеми (яку дізналися люди мого покоління та використовувались для програми місяця Аполлона) ви складаєте діаграму з блоками для виконання заяв та алмазами для рішень, і ви могли з’єднати їх лініями, які проходять повсюдно. (Так званий "код спагетті".)

Проблема коду спагетті полягає в тому, що ви, програміст, могли «знати», що це правильно, але як ви могли це довести собі чи комусь іншому? Насправді це насправді може мати потенційну неправильну поведінку, і ваше знання про те, що це завжди правильно, може бути помилковим.

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

Звичайно, люди ще можуть написати код спагетті навіть без нього goto. Загальним методом є написання а while(...) switch( iState ){..., де різні випадки встановлюють iStateрізні значення. Насправді в C або C ++ можна написати макрос для цього і назвати його GOTO, так що мовляв, ви не використовуєте goto, це відмінність без різниці.

Як приклад того, як доказ коду може перешкоджати необмеженому goto, дуже давно я натрапив на структуру управління, корисну для динамічно змінюваних інтерфейсів користувача. Я назвав це диференціальним виконанням . Це повністю Тьюринга універсальне, але його доказ коректності залежить від чисто структурного програмування - немає goto, return, continue, break, або виключення.

введіть тут опис зображення


49
Правильність не може бути доведена, за винятком самих тривіальних програм, навіть якщо для їх написання використовується структуроване програмування. Ви можете лише підвищити рівень своєї впевненості; Структуроване програмування цього досягає.
Роберт Харві

23
@MikeDunlavey: Пов'язане: "Остерігайтеся помилок у наведеному вище коді; я лише довів це правильно, а не пробував". staff.fnwi.uva.nl/p.vanemdeboas/knuthnote.pdf
Mooing Duck

26
@RobertHarvey Не зовсім вірно, що докази коректності практичні лише для тривіальних програм. Однак необхідні більш спеціалізовані інструменти, ніж структуроване програмування.
Майк Хаскель

18
@MSalters: Це дослідницький проект. Мета такого дослідницького проекту - показати, що вони могли написати перевірений компілятор, якби мали ресурси. Якщо ви подивитесь на їхню теперішню роботу, то побачите, що вони просто не зацікавлені у підтримці пізніших версій C, скоріше вони зацікавлені у розширенні властивостей правильності, які вони можуть довести. І для цього просто абсолютно не важливо, підтримують вони C90, C99, C11, Pascal, Fortran, Algol або Plankalkül.
Йорг W Міттаг

8
@martineau: Мені прислухаються до тих "бозонових фраз", де програмісти начебто відповідають, погоджуючись, що це погано чи добре, бо вони сидять, якщо цього не роблять. Мають бути більш основні причини для речей.
Майк Данлаве

63

Чому гото небезпечно?

  • gotoне викликає нестабільність сама по собі. Незважаючи на близько 100 000 gotoс, ядро ​​Linux все ще є моделлю стабільності.
  • gotoсам по собі не повинен викликати вразливості безпеки. Однак у деяких мовах змішування його з блоками управління try/ catchвиключення може призвести до вразливості, як це пояснено в цій рекомендації CERT . Основні компілятори C ++ позначають і запобігають таким помилкам, але, на жаль, старіші або більш екзотичні компілятори цього не роблять.
  • gotoвикликає нечитабельний і нездійсненний код. Це також називається кодом спагетті , оскільки, як і в тарілці зі спагетті, дуже важко стежити за потоком контролю, коли є занадто багато гото.

Навіть якщо вам вдасться уникнути коду спагетті, і якщо ви використовуєте лише кілька готів, вони все одно полегшують помилки, як і витік ресурсів:

  • Код за допомогою структурного програмування, з чіткими вкладеними блоками та петлями або комутаторами, легко дотримуватися; його потік управління дуже передбачуваний. Тому легше забезпечити повагу інваріантів.
  • З gotoтвердженням ви перериваєте цей прямий потік і ламаєте очікування. Наприклад, ви можете не помітити, що вам все-таки потрібно звільнити ресурси.
  • Багато gotoв різних місцях можуть відправити вас до однієї цілі переходу. Тож не очевидно знати напевно, у якому стані ви знаходитесь, коли доїдете до цього місця. Отже, ризик зробити помилкові / необгрунтовані припущення є досить великим.

Додаткова інформація та цитати:

C надає нескінченно зловживану gotoзаяву та мітки, на які слід відгалужуватись. Формально gotoце ніколи не потрібно, і на практиці майже завжди легко писати код без нього. (...)
Тим не менш, ми запропонуємо кілька ситуацій, коли гото може знайти місце. Найбільш поширене використання - це відмовитися від обробки в деяких глибоко вкладених структурах, таких як прорив відразу двох циклів. (...)
Хоча ми не догматичні щодо цього питання, але здається, що goto-твердження слід застосовувати досить щадно, якщо вони взагалі є .

  • Джеймс Гослінг та Генрі МакГілтон написали у своєму текстовому документі для мови Java 1995 року :

    Більше немає Goto Statements У
    Java немає goto-заяви. Дослідження ілюструють, що гото (помилково) використовується частіше, ніж просто «тому, що воно є». Усунення goto призвело до спрощення мови (...) Дослідження приблизно на 100 000 рядків коду С визначили, що приблизно 90 відсотків висловлювань goto використовуються виключно для отримання ефекту виривання вкладених циклів. Як було сказано вище, багаторівневий перерва та продовження усувають більшість потреб у операторах goto.

  • Bjarne Stroustrup визначає goto у своєму словнику в цих привабливих умовах:

    goto - сумнозвісний гото. Переважно корисний у машині, створеному кодом C ++.

Коли можна було б використовувати?

Як і K&R, я не догматично ставився до готів. Я визнаю, що бувають ситуації, коли гото може полегшити життя.

Як правило, в C goto дозволяє виходити з багаторівневого циклу або обробляти помилки, вимагаючи досягти відповідної точки виходу, що звільняє / розблоковує всі ресурси, що були виділені до цього часу (неодноразовий розподіл у послідовності означає кілька міток). Ця стаття кількісно визначає різні способи використання goto в ядрі Linux.

Особисто я вважаю за краще уникати цього і за 10 років С я використав максимум 10 готів. Я вважаю за краще використовувати вкладені ifs, які, на мою думку, є більш читабельними. Коли це призведе до надто глибокого вкладення, я б вирішив або розкласти свою функцію на менші частини, або використовувати бульний індикатор у каскаді. Сьогоднішні оптимізуючі компілятори досить розумні, щоб генерувати майже той самий код, ніж той самий код goto.

Використання goto значно залежить від мови:

  • У C ++, правильне використання RAII змушує компілятор автоматично знищувати об'єкти, що виходять за межі сфери, так що ресурси / блокування будуть очищені в будь-якому випадку, і більше немає потреби в goto.

  • У Java немає необхідності Гото (див автора цитати в Java вище , і цей чудовий Stack Overflow відповідь ): збирач сміття , який очищає безлад, break, continueі try/ catchобробка винятків охоплює всі той випадок , коли gotoможе бути корисними, але в більш безпечному і краще манера. Популярність Java доводить, що готові твердження можна уникнути сучасною мовою.

Масштабування знаменитого вразливості SSL перебуває у відмові

Важлива відмова: з огляду на жорстоку дискусію в коментарях, я хочу уточнити, що я не претендую на те, що заява goto є єдиною причиною цієї помилки. Я не претендую, що без Goto не було б жодної помилки. Я просто хочу показати, що гото може бути причетним до серйозної помилки.

Я не знаю, скільки серйозних помилок пов’язано gotoз історією програмування: деталі часто не повідомляються. Однак була відома помилка Apple SSL, яка послабила безпеку iOS. Заява, яка призвела до цієї помилки, була неправильною gotoзаявою.

Деякі стверджують, що першопричиною помилки була не сама заява goto, а неправильна копія / вставка, оманливий відступ, відсутні фігурні дужки навколо умовного блоку чи, можливо, робочі звички розробника. Я не можу ані підтвердити жодного з них: всі ці аргументи є ймовірними гіпотезами та інтерпретацією. Ніхто насправді не знає. ( тим часом, гіпотеза про злиття, що пішла не так, як хтось запропонував у коментарях, здається, дуже хорошим кандидатом з огляду на деякі інші невідповідності відступу в тій же функції ).

Єдиний об'єктивний факт полягає в тому, що дублювання gotoпризвело до того, щоб передчасно вийти з функції. Дивлячись на код, єдиним іншим твердженням, яке могло викликати той же ефект, було б повернення.

Помилка функціонує SSLEncodeSignedServerKeyExchange()в цьому файлі :

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
        goto fail;
    if ((err =...) !=0)
        goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
        goto fail;
        goto fail;  // <====OUCH: INDENTATION MISLEADS: THIS IS UNCONDITIONDAL!!
    if (...)
        goto fail;

    ... // Do some cryptographic operations here

fail:
    ... // Free resources to process error

Дійсно фігурні дужки навколо умовного блоку могли запобігти помилці:
це призвело б або до синтаксичної помилки під час компіляції (і, отже, виправлення), або до зайвого нешкідливого goto. До речі, GCC 6 зможе помітити ці помилки завдяки додатковому попередженню для виявлення непослідовних відступів.

Але, по-перше, усіх цих готів можна було б уникнути більш структурованим кодом. Тож гото принаймні опосередковано є причиною цієї помилки. Існують щонайменше два різні способи, які могли б цього уникнути:

Підхід 1: якщо пункт або вкладено ifs

Замість того, щоб послідовно перевіряти безліч умов на помилку, і кожного разу надсилаючи failярлики у разі проблеми, можна було б ifобрати для виконання криптографічних операцій у висловленні, що зробило б це, лише якщо не було неправильної попередньої умови:

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0 &&
        (err = ...) == 0 ) &&
        (err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0) &&
        ...
        (err = ...) == 0 ) )
    {
         ... // Do some cryptographic operations here
    }
    ... // Free resources

Підхід 2: використовувати акумулятор помилок

Цей підхід заснований на тому, що майже всі тут твердження викликають якусь функцію для встановлення errкоду помилки, а решту коду виконують лише, якщо errбуло 0 (тобто функція виконується без помилок). Приємною безпечною та читаною альтернативою є:

bool ok = true;
ok =  ok && (err = ReadyHash(&SSLHashSHA1, &hashCtx))) == 0;
ok =  ok && (err = NextFunction(...)) == 0;
...
ok =  ok && (err = ...) == 0;
... // Free resources

Тут немає жодного гото: немає ризику швидко перескочити до точки виходу з ладу. І візуально було б легко помітити нерівну лінію або забуту ok &&.

Ця конструкція є більш компактною. Він заснований на тому, що в C друга частина логічного і ( &&) оцінюється лише за умови, що перша частина є істинною. Насправді асемблер, що генерується оптимізуючим компілятором, майже еквівалентний вихідному коду з gotos: Оптимізатор дуже добре виявляє ланцюг умов і генерує код, який при першому ненульовому поверненому значенні стрибає до кінця ( онлайн-доказ ).

Можна навіть передбачити перевірку узгодженості в кінці функції, яка могла б на етапі тестування виявити невідповідність між прапором ОК та кодом помилки.

assert( (ok==false && err!=0) || (ok==true && err==0) );

Помилки, такі як ==0ненароком замінені !=0помилки або логічні з'єднувачі, легко виявлятимуться на етапі налагодження.

Як було сказано: Я не претендую на те, що альтернативні конструкції уникали б жодної помилки. Я просто хочу сказати, що вони могли ускладнити виникнення помилок.


34
Ні, помилка Apple goto fail була спричинена розумним програмістом, який вирішив не включати фігурні дужки після оператора if. :-) Отже, коли з'явився наступний програміст технічного обслуговування (або той самий, однакова різниця) і додав ще один рядок коду, який повинен був виконуватися лише тоді, коли оператор if оцінюється як істинний, оператор запускається кожного разу. Бам, помилка "goto fail", яка була головною помилкою у безпеці. Але це по суті не мало нічого спільного з тим, що було використано заяву goto.
Крейг

47
Помилка Apple "goto fail" безпосередньо була викликана дублюванням рядка коду. Особливий ефект цієї помилки був викликаний тим, що рядок є gotoвнутрішньою ifзаявою без дужок. Але якщо ви припускаєте, що уникнення gotoзробить ваш код незахищеним від впливу випадково дублюваних рядків, у мене є погані новини для вас.
іммібіс

12
Ви використовуєте швидку булеву поведінку оператора для виконання оператора через побічний ефект і стверджуєте, що це більш зрозуміло, ніж ifтвердження? Веселі часи. :)
Крейг

38
Якби замість "goto fail" було б твердження "fail = true", то б саме сталося.
gnasher729

15
Цей помилка була викликана неохайним розробником, який не звертав уваги на те, що він чи вона робить, і не читає власні зміни коду. Нічого більше, нічого менше. Гаразд, можливо, також вкиньте тестування.
Гонки легкості по орбіті

34

Знаменита стаття Dijkstra була написана в той час, коли деякі мови програмування насправді здатні створювати підпрограми, що мають кілька точок входу та виходу. Іншими словами, ви можете буквально стрибнути в середину функції та вистрибнути з будь-якого місця в межах функції, фактично не викликаючи цю функцію та не повертаючись з неї звичайним способом. Це все ще стосується мови складання. Ніхто ніколи не сперечається з тим, що такий підхід перевершує структурований метод написання програмного забезпечення, який ми зараз використовуємо.

У більшості сучасних мов програмування функції дуже конкретно визначені одним входом та однією точкою виходу. Точкою входу є те, де ви вказуєте параметри функції та називаєте її, а точка виходу - це те, де ви повертаєте отримане значення та продовжуєте виконання за інструкцією після вихідного виклику функції.

У межах цієї функції ви повинні мати можливість робити все, що завгодно, в межах розуму. Якщо введення функції або двох в функцію робить це зрозумілішим або покращує вашу швидкість, чому б ні? Вся суть функції полягає в секвеструванні трохи чітко визначеної функціональності, так що вам більше не доведеться думати про те, як вона працює всередині. Після того, як це написано, ви просто використовуєте його.

І так, ви можете мати кілька операторів повернення всередині функції; У правильній функції, з якої ви повертаєтесь, завжди є одне місце (в основному зворотна частина функції). Це зовсім не те саме, що вискакувати з функції з goto, перш ніж вона має шанс правильно повернутися.

введіть тут опис зображення

Тому справа не в тому, щоб використовувати Goto. Йдеться про уникнення їх зловживань. Всі погоджуються, що ви можете зробити жахливо перекручену програму, використовуючи gotos, але ви можете зробити те ж саме, зловживаючи функціями (просто набагато простіше зловживати gotos).

Для чого це варто, так як я закінчив програми BASIC у стилі рядка чисел до структурованого програмування з використанням мов Pascal та фігурних брекетів, мені ніколи не доводилося використовувати goto. Єдиний раз, коли я спокусився його використати - це зробити ранній вихід із вкладеного циклу (мовами, які не підтримують багаторівневий ранній вихід із циклів), але я зазвичай можу знайти інший спосіб, який є більш чистим.


2
Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
maple_shaft

11

До яких помилок призводять «goto» заяви? Чи є історично значущі приклади?

Я використовував gotoвисловлювання, коли писав програми BASIC в дитинстві як простий спосіб отримати петлі та в той час (Commodore 64 BASIC не мав циклів while, і я був занадто незрілим, щоб навчитися правильному синтаксису та використанню for-циклів ). Мій код часто був тривіальним, але будь-які помилки циклу можна було негайно віднести до мого використання goto.

Зараз я в основному використовую Python, мову програмування високого рівня, яка визначила, що він не потребує goto.

Коли Едсгер Дайкстра заявив, що "Гото вважається шкідливим" в 1968 році, він не наводив декількох прикладів, коли пов’язані помилки можна звинуватити в гото, швидше за все, він заявив, що gotoце не потрібно для мов вищого рівня і що його слід уникати на користь того, що ми сьогодні вважаємо нормальним контрольний потік: петлі та умовні умови. Його слова:

Нестримне використання переходу до негайного наслідку стає важко знайти значущий набір координат, в яких можна описати хід процесу.
[...] перейти до заяви , оскільки це варто тільки занадто примітивно; це занадто велике запрошення заплутати свою програму.

Ймовірно, він мав гори з прикладами помилок кожного разу, коли він налагоджував код gotoу ньому. Але його документ був узагальненим твердженням про позицію, підкріпленим доказами, які gotoне потрібні мовам вищого рівня. Узагальнена помилка полягає в тому, що ви, можливо, не маєте можливості статично аналізувати розглянутий код.

Код набагато складніше статично аналізувати за допомогою gotoвисловлювань, особливо якщо ви відскакуєте назад у своєму потоці управління (що я раніше робив) або в якийсь незв'язаний розділ коду. Код може і був написаний таким чином. Це було зроблено для високооптимізації для дуже дефіцитних обчислювальних ресурсів і, таким чином, архітектури машин.

Апокрифний приклад

Існувала програма "блекджек", яка, за супроводженням коду, підтримала технічне обслуговування досить елегантно оптимізованого, але також неможливого для "виправлення" . Це було запрограмовано в машинному коді, який сильно залежить від gotos - тому я вважаю цю історію досить актуальною. Це найкращий канонічний приклад, про який я можу придумати.

Контрприклад

Однак джерело C CPython (найпоширеніша та посилальна реалізація Python) використовує gotoоперації з великим ефектом. Вони використовуються для обходу невідповідного потоку управління всередині функцій, щоб дійти до кінця функцій, роблячи функції більш ефективними, не втрачаючи читабельності. Це поважає один із ідеалів структурованого програмування - мати єдину точку виходу для функцій.

Для запису, я вважаю цей зустрічний приклад досить легко статично проаналізувати.


5
"Історія Мела", з якою я стикався, опинилася в дивній архітектурі, де (1) кожна машинна мова мови виконує перехід після її роботи, і (2) було дуже неефективно розміщувати послідовність інструкцій у послідовних місцях пам'яті. І програма була написана вручну, оптимізована складанням, а не мовою компіляції.

@MilesRout Я думаю, що ви тут можете бути правильними, але на сторінці вікіпедії щодо структурованого програмування написано: "Найпоширенішим відхиленням, яке зустрічається у багатьох мовах, є використання оператора повернення для раннього виходу з підпрограми. Це призводить до кількох точок виходу. , замість єдиної точки виходу, необхідної для структурованого програмування. " - ти кажеш, що це неправильно? Чи є у вас джерело для цитування?
Аарон Хол

@AaronHall Це не початкове значення.
Майлз Руут

11

Коли вігвам був сучасним архітектурним мистецтвом, їхні будівельники могли, безперечно, дати точні практичні поради щодо будівництва вігвам, як дозволити диму виходити тощо. На щастя, сьогоднішні будівельники можуть, напевно, забути більшість цих порад.

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

Коли перфокарти були сучасним мистецтвом програмування, їхні практикуючі могли б також дати точні практичні поради щодо організації карт, як нумерації виписок тощо. Я не впевнений, що ця порада сьогодні дуже актуальна.

Ви досить старі, навіть щоб знати фразу "для нумерації виписок" ? Вам цього не потрібно знати, але якщо ви цього не зробите, то ви не знайомі з історичним контекстом, в якому попередження проти gotoбуло принципово актуальним.

Попередження проти gotoсьогодні просто не дуже актуальне. З базовим навчанням під час / для циклів та функціональних викликів ви навіть не думаєте видавати gotoдуже часто. Коли ви це думаєте, у вас, мабуть, є причина, тому йдіть далі.

Але чи можна gotoзаявою не зловживати?

Відповідь: звичайно, цим можна зловживати, але його зловживання є досить крихітною проблемою в інженерії програмного забезпечення порівняно з набагато більш поширеними помилками, такими як використання змінної, де буде обслуговуватися константа, або, наприклад, програмування "вирізати та вставити" (інакше відомий як зневага до рефактора). Сумніваюсь, що ти в великій небезпеці. Якщо ви не використовуєте longjmpчи іншим чином не передаєте контроль на далекий код, якщо ви думаєте скористатися gotoабо ви просто хочете спробувати це для розваги, продовжуйте. Ви будете добре.

Ви можете помітити відсутність останніх страшилок, в яких gotoграє лиходій. Більшість чи всі ці історії, здається, мають 30 чи 40 років. Ви стоїте на твердій грунті, якщо вважаєте ці історії здебільшого застарілими.


1
Я бачу, що я привернув хоча б один потік. Ну, це і слід було очікувати. Можливо, прийде більше подій. Я не збираюся давати загальноприйняту відповідь, хоча, коли десятиліття досвіду програмування навчили мене, що в цьому випадку звичайна відповідь трапиться неправильно. Це, на мою думку, будь-який випадок. Я навів свої причини. Читач може вирішити.
вт

1
Ваше повідомлення дуже добре, я не можу оскаржувати коментарів.
Пітер Б

7

Щоб додати одну річ до інших відмінних відповідей, із gotoтвердженнями може бути важко сказати, як саме ви потрапили до будь-якого місця програми. Ви можете знати, що виняток стався в якомусь конкретному рядку, але якщо вони є gotoв коді, немає ніякого способу сказати, які оператори, що виконуються, призводять до цього стану, викликаючи стан, без пошуку всієї програми. Немає стека викликів і візуального потоку. Цілком можливо, що існує вислів на 1000 рядків, який ставить вас у невдалий стан, виконуючи а gotoдо рядка, який викликав виняток.


1
Visual Basic 6 та VBA мають болісне поводження з помилками, але якщо ви користуєтесь On Error Goto errh, ви можете використовувати Resumeдля повторного спроби, Resume Nextпропустити порушувальну лінію та Erlзнати, яка вона була (якщо ви використовуєте номери рядків).
Cees Timmerman

@CeesTimmerman Я дуже мало робив з Visual Basic, я цього не знав. Я додам коментар до цього.
qfwfq

2
@CeesTimmerman: Семантика "резюме" VB6 жахлива, якщо виникає помилка в підпрограмі, яка не має власного блоку помилок. Resumeвідновить цю функцію з самого початку і Resume Nextпропустить усе, що ще не функціонувало.
supercat

2
@supercat Як я вже сказав, це боляче. Якщо вам потрібно знати точний рядок, вам слід пронумерувати їх усі або тимчасово відключити обробку помилок On Error Goto 0і вручну налагодити. Resumeпризначений для використання після перевірки Err.Numberта внесення необхідних коригувань.
Cees Timmerman

1
Слід зазначити, що сучасною мовою gotoпрацює лише з міченими лініями, які, здається, були замінені міченими петлями .
Cees Timmerman

7

Гото для людей важче міркувати, ніж інші форми контролю потоку.

Програмувати правильний код важко. Написати правильні програми важко, визначити, чи правильно вони є, важко довести правильність програм.

Отримати код, щоб нечітко робити те, що ви хочете, легко порівняно з усім іншим щодо програмування.

goto вирішує деякі програми, отримуючи код, щоб робити те, що ви хочете. Це не допомагає полегшити перевірку правильності, хоча її альтернативи часто роблять.

Є стилі програмування, де goto - це відповідне рішення. Існують навіть стилі програмування, де його злий близнюк, що з'являється, є відповідним рішенням. В обох цих випадках слід бути надзвичайно обережними, щоб переконатися, що ви використовуєте його за зрозумілою схемою, і багато вручну перевірити, що ви не робите чогось важкого, щоб забезпечити правильність.

Як приклад, є особливість деяких мов під назвою coututines. Супроводи - це нитки, що не мають ниток; стан виконання без потоку для запуску. Ви можете попросити їх виконати, і вони можуть запустити частину себе, а потім призупинити себе, передаючи управління потоком назад.

"Неглибокі" розробки мовами без підтримки кореневих програм (наприклад, C ++ pre-C ++ 20 і C) можливі за допомогою суміші gotos та ручного управління державою. "Глибокі" розробки можуть бути виконані за допомогою setjmpі longjmpфункцій C.

Бувають випадки, коли спроби настільки корисні, що писати їх вручну і ретельно варто того.

Що стосується C ++, вони вважаються достатньо корисними, що вони розширюють мову для їх підтримки. Управління gotoдержавним та ручним режимом ховається за рівнем абстрагування з нульовою вартістю, що дозволяє програмістам записувати їх без труднощів з необхідністю доводити свою безладність goto, void**s, ручне будівництво / руйнування штату тощо.

Гото отримує приховані за більш високого рівня абстракції, як whileабо forабо ifабо switch. Ці абстракції вищого рівня легше довести правильність і перевірити.

Якщо в мові бракувало деяких з них (наприклад, у деяких сучасних мовах відсутні координати), або обмеження разом із шаблоном, який не відповідає проблемі, або використання goto, стає вашою альтернативою.

Отримати комп'ютер нечітко робити те, що ви хочете, щоб це робилося в звичайних випадках, просто . Написати надійний надійний код важко . Гото допомагає першому набагато більше, ніж допомагає другому; отже, "goto вважається шкідливим", оскільки це ознака поверхово "працюючого" коду з глибоким і важким відстеженням помилок. Досягнувши достатнього зусилля, ви все ще можете зробити код з "goto" надійно надійним, тому дотримуватись правила абсолюту неправильно; але, як правило, це добре.


1
longjmpзаконний лише в C для відкручування стека до а setjmpв батьківській функції. (Як і прорив декількох рівнів вкладення, але для викликів функцій замість циклів). longjjmpдо контексту, setjmpзробленого у функції, яка повернулася з тих пір, не є законною (і я підозрюю, що це не спрацює на практиці в більшості випадків). Я не знайомий із спільними програмами, але з вашого опису спільної програми, подібної до потоку в просторі користувача, я думаю, що він потребує власного стеку, а також контексту збереженого реєстру, і longjmpлише дає вам реєстрацію -збереження, а не окремий стек.
Пітер Кордес

1
О, я повинен був би просто гугл : fanf.livejournal.com/105413.html описує створення місця для стеку для запуску програми. (Що, як я підозрював, необхідне, крім простого використання setjmp / longjmp).
Пітер Кордес

2
@PeterCordes: Реалізація не вимагає надання будь-яких засобів, за допомогою яких код може створити пару jmp_buff, яка підходить для використання в якості посібника. Деякі реалізації вказують засоби, за допомогою яких код може створювати його, навіть якщо Стандарт не вимагає від них такої можливості. Я б не описував код, який спирається на такі методи, як "стандартний C", оскільки у багатьох випадках jmp_buff буде непрозорою структурою, яка не гарантовано поводиться послідовно між різними компіляторами.
supercat

6

Погляньте на цей код із http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html, який насправді був частиною великого моделювання дилеми в'язня. Якщо ви бачили старий код FORTRAN або BASIC, ви зрозумієте, що це не так незвично.

C  Not nice rules in second round of tour (cut and pasted 7/15/93)
   FUNCTION K75R(J,M,K,L,R,JA)
C  BY P D HARRINGTON
C  TYPED BY JM 3/20/79
   DIMENSION HIST(4,2),ROW(4),COL(2),ID(2)
   K75R=JA       ! Added 7/32/93 to report own old value
   IF (M .EQ. 2) GOTO 25
   IF (M .GT. 1) GOTO 10
   DO 5 IA = 1,4
     DO 5 IB = 1,2
5  HIST(IA,IB) = 0

   IBURN = 0
   ID(1) = 0
   ID(2) = 0
   IDEF = 0
   ITWIN = 0
   ISTRNG = 0
   ICOOP = 0
   ITRY = 0
   IRDCHK = 0
   IRAND = 0
   IPARTY = 1
   IND = 0
   MY = 0
   INDEF = 5
   IOPP = 0
   PROB = .2
   K75R = 0
   RETURN

10 IF (IRAND .EQ. 1) GOTO 70
   IOPP = IOPP + J
   HIST(IND,J+1) = HIST(IND,J+1) + 1
   IF (M .EQ. 15 .OR. MOD(M,15) .NE. 0 .OR. IRAND .EQ. 2) GOTO 25
   IF (HIST(1,1) / (M - 2) .GE. .8) GOTO 25
   IF (IOPP * 4 .LT. M - 2 .OR. IOPP * 4 .GT. 3 * M - 6) GOTO 25
   DO 12 IA = 1,4
12 ROW(IA) = HIST(IA,1) + HIST(IA,2)

   DO 14 IB = 1,2
     SUM = .0
     DO 13 IA = 1,4
13   SUM = SUM + HIST(IA,IB)
14 COL(IB) = SUM

   SUM = .0
   DO 16 IA = 1,4
     DO 16 IB = 1,2
       EX = ROW(IA) * COL(IB) / (M - 2)
       IF (EX .LE. 1.) GOTO 16
       SUM = SUM + ((HIST(IA,IB) - EX) ** 2) / EX
16 CONTINUE

   IF (SUM .GT. 3) GOTO 25
   IRAND = 1
   K75R = 1
   RETURN

25 IF (ITRY .EQ. 1 .AND. J .EQ. 1) IBURN = 1
   IF (M .LE. 37 .AND. J .EQ. 0) ITWIN = ITWIN + 1
   IF (M .EQ. 38 .AND. J .EQ. 1) ITWIN = ITWIN + 1
   IF (M .GE. 39 .AND. ITWIN .EQ. 37 .AND. J .EQ. 1) ITWIN = 0
   IF (ITWIN .EQ. 37) GOTO 80
   IDEF = IDEF * J + J
   IF (IDEF .GE. 20) GOTO 90
   IPARTY = 3 - IPARTY
   ID(IPARTY) = ID(IPARTY) * J + J
   IF (ID(IPARTY) .GE. INDEF) GOTO 78
   IF (ICOOP .GE. 1) GOTO 80
   IF (M .LT. 37 .OR. IBURN .EQ. 1) GOTO 34
   IF (M .EQ. 37) GOTO 32
   IF (R .GT. PROB) GOTO 34
32 ITRY = 2
   ICOOP = 2
   PROB = PROB + .05
   GOTO 92

34 IF (J .EQ. 0) GOTO 80
   GOTO 90

70 IRDCHK = IRDCHK + J * 4 - 3
   IF (IRDCHK .GE. 11) GOTO 75
   K75R = 1
   RETURN

75 IRAND = 2
   ICOOP = 2
   K75R = 0
   RETURN

78 ID(IPARTY) = 0
   ISTRNG = ISTRNG + 1
   IF (ISTRNG .EQ. 8) INDEF = 3
80 K75R = 0
   ITRY = ITRY - 1
   ICOOP = ICOOP - 1
   GOTO 95

90 ID(IPARTY) = ID(IPARTY) + 1
92 K75R = 1
95 IND = 2 * MY + J + 1
   MY = K75R
   RETURN
   END

Тут є багато питань, що виходять далеко за межі заяви GOTO; Я чесно думаю, що заява GOTO була трохи козлом відпущення. Але контрольний потік тут абсолютно не зрозумілий, і код змішується разом таким чином, щоб зробити його незрозумілим, що відбувається. Навіть без додавання коментарів або використання кращих імен змінних, зміна цього на структуру блоку без GOTOs значно полегшила б їх читання та дотримання.


4
так правда :-) Можливо, варто відзначити: у спадщини FORTAN та BASIC не було структурних блоків, так що, якщо ТОП-пункт не міг би утримуватися в одному рядку, GOTO була єдиною альтернативою.
Крістоф

Текстові мітки замість чисел або хоча б коментарі до пронумерованих рядків допоможуть дуже багато.
Cees Timmerman

Повернувшись до моїх днів FORTRAN-IV: я обмежив використання GOTO для реалізації блоків IF / ELSE IF / ELSE, WHILE і BREAK та NEXT у циклах. Хтось показав мені зло коду спагетті, і чому ви повинні структурувати, щоб уникнути цього, навіть якщо вам доведеться реалізувати свої блокові структури за допомогою GOTO. Пізніше я почав використовувати попередній процесор (RATFOR, IIRC). Goto не є суттєво злим, це просто надмірна конструкція низького рівня, яка перетворюється на інструкцію гілки асемблера. Якщо вам доведеться використовувати його через те, що ваша мова є недостатньою, використовуйте її для побудови або розширення належних структур блоків.
nigel222

@CeesTimmerman: Це сорт FORTRAN IV для садових сортів. Текстові мітки не підтримувалися в FORTRAN IV. Я не знаю, чи підтримують їх новіші версії мови. Коментарі в тому ж рядку, що і код, не підтримувались у стандартному FORTRAN IV, хоча для їх підтримки, можливо, були розширення, пов’язані з продавцем. Я не знаю, що говорить "поточний" стандарт FORTRAN: я давно відмовився від FORTRAN, і не сумую за цим. (Найновіший код FORTRAN, який я бачив, сильно нагадує PASCAL початку 1970-х.)
Джон Р. Стром

1
@CeesTimmerman: розширення, яке стосується конкретного постачальника. Код взагалі ДУЖЕ сором'язливий до коментарів. Крім того, існує конвенція, яка в деяких місцях стала загальною, щоб розміщувати номери рядків тільки на операторах CONTINUE та FORMAT, щоб було простіше додавати рядки пізніше. Цей код не відповідає цій конвенції.
Джон Р. Стром

0

Одним із принципів сталої програмування є інкапсуляція . Справа в тому, що ви інтерфейсуєте з модулем / підпрограмою / підпрограмою / компонентом / об'єктом, використовуючи визначений інтерфейс, і лише цей інтерфейс, і результати будуть передбачуваними (якщо припустити, що блок був ефективно перевірений).

У межах однієї одиниці коду застосовується той самий принцип. Якщо ви послідовно застосовуєте структуровані програми або принципи об'єктно-орієнтованого програмування, ви не будете:

  1. створювати несподівані шляхи через код
  2. надходять у розділи коду з невизначеними або недопустимими змінними значеннями
  3. вийти з шляху коду з невизначеними або недопустимими змінними значеннями
  4. не вдалося виконати блок транзакцій
  5. залиште в пам’яті частини коду чи даних, які фактично мертві, але непридатні для очищення та перерозподілу сміття, оскільки ви не сигналізували про їх звільнення за допомогою явної конструкції, яка викликає їх вивільнення, коли шлях управління кодом залишає блок

Деякі з найпоширеніших симптомів цих помилок обробки включають витоки пам’яті, накопичення пам’яті, переповнення покажчиків, збої, неповні записи даних, винятки додавання / чг / видалення, помилки сторінки пам’яті тощо.

До виявлених користувачами проявів цих проблем відносяться блокування користувальницького інтерфейсу, поступово зменшення продуктивності, неповні записи даних, неможливість ініціювання або завершення транзакцій, пошкоджені дані, відключення мережі, відключення електроенергії, збої вбудованих систем (від втрати контролю над ракетами). до втрати можливості відстеження та контролю в системах управління повітряним рухом), відмов таймера тощо тощо. Каталог дуже великий.


3
Ви відповіли, не згадуючи gotoжодного разу. :)
Роберт Харві

0

гото небезпечно, оскільки його зазвичай використовують там, де це не потрібно. Використовувати все, що не потрібно - це небезпечно, але особливо гото . Якщо ви перебуваєте в Google, ви знайдете безліч помилок, викликаних goto, це сама по собі не є причиною його використання (помилки завжди трапляються, коли ви використовуєте мовні функції, тому що це невід'ємно в програмуванні), але деякі з них явно дуже пов'язані для Гото використання.


Причини використання / не використання goto :

  • Якщо вам потрібна петля, ви повинні використовувати whileабо for.

  • Якщо вам потрібно зробити умовний стрибок, використовуйте if/then/else

  • Якщо вам потрібна процедура, викликайте функцію / метод.

  • Якщо вам потрібно вийти з функції, просто return.

Я можу порахувати на пальцях місця, де я бачив, як gotoправильно використовував та використовував.

  • CPython
  • libKTX
  • певно, ще кілька

У libKTX є функція, яка має наступний код

if(something)
    goto cleanup;

if(bla)
    goto cleanup;

cleanup:
    delete [] array;

Зараз у цьому місці gotoкорисно, оскільки мова:

  • ми всередині функції
  • ми не можемо записати функцію очищення (тому що ми вводимо іншу область, а надання стану доступної функції виклику є більш тяжким)

Цей випадок використання корисний, оскільки в C у нас немає класів, тому найпростішим способом очищення є використання a goto.

Якби у нас був той самий код у C ++, гото вже не потрібно:

class MyClass{
    public:

    void Function(){
        if(something)
            return Cleanup(); // invalid syntax in C#, but valid in C++
        if(bla)
            return Cleanup(); // invalid syntax in C#, but valid in C++
    }

    // access same members, no need to pass state (compiler do it for us).
    void Cleanup(){

    }



}

До яких помилок це може призвести? Що завгодно. Нескінченні петлі, неправильний порядок виконання, укладка гвинта ..

Задокументований випадок - це вразливість SSL, яка дозволила людині в атаках посередині, викликаних неправильним використанням goto: ось стаття

Це помилка друку, але на деякий час залишилась непоміченою, якби код був структурований іншим способом, перевірити належним чином таку помилку неможливо було б.


2
Є багато коментарів до однієї відповіді, які дуже чітко пояснюють, що використання "goto" було абсолютно невідповідним щодо SSL-помилки - помилка була викликана необережним дублюванням одного рядка коду, тож колись він виконувався умовно, а один раз безумовно.
gnasher729

Так, я чекаю кращого історичного прикладу помилки Goto, перш ніж прийму. Як сказано; Це було б неважливо, що було в цьому рядку коду; Відсутність фігурних дужок виконало б це і призвело до помилки незалежно. Мені цікаво хоч; що таке "гвинтовий гвинт"?
Аківа

1
Приклад з коду, над яким я працював раніше в PL / 1 CICS. Програма виставила екран, що складається з однорядних карт. Після повернення екрана він знайшов масив надісланих карт. Елементи цього використовували для пошуку індексу в одному масиві, а потім використовували цей індекс для індексу змінних масивів, щоб зробити GOTO, щоб він міг обробляти цю окрему карту. Якщо масив надісланих карт був невірним (або був пошкоджений), він може спробувати зробити GOTO до неправильної мітки, або ще гірше аварії, оскільки він отримав доступ до елемента після закінчення масиву змінних міток. Переписаний для використання синтаксису типу регістру.
Кікстарт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.