Чи коли-небудь вигідніше використовувати "goto" мовою, яка підтримує цикли та функції? Якщо так, то чому?


201

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

Відповіді:


242

Є кілька причин використання заяви «goto», про яку я знаю (деякі з них уже говорили):

Чіткий вихід із функції

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

Вихід із вкладених циклів

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

Поліпшення продуктивності низького рівня

Це справедливо лише в перфрікритичному коді, але оператори goto виконують дуже швидко і можуть дати вам імпульс під час переміщення через функцію. Це, однак, меч з двома кінцями, оскільки компілятор, як правило, не може оптимізувати код, що містить gotos.

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


15
Правильний спосіб виходу з вкладених циклів - переробляти внутрішній цикл окремим методом.
Язон

129
@Jason - Ба. Це вантаж бика. Замінити gotoна returnце просто нерозумно. Це не "рефакторинг" нічого, це просто "перейменування", щоб люди, які виросли в умовах gotoпригніченого середовища (тобто всіх нас), почували себе краще використовувати те, що морально означає goto. Я набагато вважаю за краще бачити цикл там, де я його використовую, і бачити трохи goto, що саме по собі є лише інструментом , ніж бачити, як хтось перемістив цикл кудись не пов'язаним, просто щоб уникнути goto.
Кріс Лутц

18
Є місця, де важливі готи: наприклад, середовище C ++, яке не має винятків. У джерелі Silverlight у нас є десятки тисяч (або більше) операторів goto для безпечної роботи за допомогою використання макросів - ключові медіа-кодеки та бібліотеки часто працюють із поверненими значеннями і ніколи не виняток, і складно поєднувати ці механізми обробки помилок у єдиний виконавський шлях.
Джефф Вілкокс

79
Варто відзначити , що всі break, continue, returnв основному goto, тільки в красивій упаковці.
el.pescado

16
Я не бачу, як do{....}while(0)це має бути кращою ідеєю, ніж goto, за винятком того, що це працює на Java.
Список Джеремі

906

Кожен, хто є антицитатамиgoto , прямо чи опосередковано, статтю GoSo Edsger Dijkstra вважав шкідливою для обґрунтування своєї позиції. Старовинна стаття Dijkstra практично не має нічого спільного з тим, як gotoвикористовуються заяви сьогодні, і, отже, те, що йдеться у статті, мало що стосується сучасної сцени програмування. У goto-Менше мем узбіччях зараз на релігію, аж до його писаннях продиктовані високо, його первосвященики та уникнення (або гірше) сприймаються єретиків.

Давайте поставимо документ Діккстра в контекст, щоб пролити трохи світла на цю тему.

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

ЦЕ "неприборкане використання твердження", що Дійкстра в 1968 р. Протидіяв у своїх роботах. ЦЕ середовище, в якому він жив, змусило його написати цей документ. Можливість стрибати куди завгодно у свій код у будь-який момент, який вам сподобався, це те, що він критикував і вимагав припинити. Порівнювати це з анемічними силами gotoна С або інших таких більш сучасних мовах просто піддається ризику.

Я вже чую піднесені співи культистів, коли вони стикаються з єретиками. "Але", вони скандують, "ви можете зробити код дуже важким для читання gotoв C." О так? Ви також можете зробити код дуже важким для читання goto. Як ця:

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

Не gotoвидно, тому читати його треба легко, правда? А як щодо цього:

a[900];     b;c;d=1     ;e=1;f;     g;h;O;      main(k,
l)char*     *l;{g=      atoi(*      ++l);       for(k=
0;k*k<      g;b=k       ++>>1)      ;for(h=     0;h*h<=
g;++h);     --h;c=(     (h+=g>h     *(h+1))     -1)>>1;
while(d     <=g){       ++O;for     (f=0;f<     O&&d<=g
;++f)a[     b<<5|c]     =d++,b+=    e;for(      f=0;f<O
&&d<=g;     ++f)a[b     <<5|c]=     d++,c+=     e;e= -e
;}for(c     =0;c<h;     ++c){       for(b=0     ;b<k;++
b){if(b     <k/2)a[     b<<5|c]     ^=a[(k      -(b+1))
<<5|c]^=    a[b<<5      |c]^=a[     (k-(b+1     ))<<5|c]
;printf(    a[b<<5|c    ]?"%-4d"    :"    "     ,a[b<<5
|c]);}      putchar(    '\n');}}    /*Mike      Laman*/

Ні gotoтам. Тому він повинен бути читабельним.

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

Тепер, якщо справедливо, деякі мовні конструкції легше зловживати, ніж інші. Якщо ви програміст на C, я б придивився набагато уважніше до приблизно 50% застосувань #defineзадовго до того, як я б пішов на хрестовий похід проти goto!

Отже, для тих, хто намагався прочитати це далеко, є кілька ключових моментів, які слід зазначити.

  1. Папери Дейкстрой на gotoзаявах були написана для середовища програмування , де gotoбуло багато потенційно більш руйнівною , ніж це відбувається в більшості сучасних мов, які не асемблер.
  2. Автоматичне відкидання будь-яких цілей використання gotoчерез це є настільки ж раціональним, як і сказати: «Я намагався колись розважитися, але це не сподобалось, тепер я проти».
  3. У gotoкоді є законним використання сучасних (анемічних) тверджень, які не можуть бути адекватно замінені іншими конструкціями.
  4. Звичайно, є неправомірні використання одних і тих же тверджень.
  5. Існують також нелегітимні способи використання сучасних контрольних висловлювань, таких як godoгидота, коли завжди помилковий doцикл виривається з використання breakзамість а goto. Вони часто гірші, ніж розумне використання goto.

42
@pocjoc: Наприклад, написання хвостово-рекурсивної оптимізації в C. Це з'являється при впровадженні функціональних перекладачів / мов виконання, але є репрезентативною для цікавої категорії проблем. Більшість компіляторів, написаних на C, використовують гото з цієї причини. Звичайно, його використання в ніші, яке не відбувається в більшості ситуацій, але в ситуаціях, для яких це вимагається, має багато сенсу використовувати.
zxq9

66
Чому всі говорять про документ «Діккстра», який вважається шкідливим, не згадуючи відповідь Кнута на нього, «Структуроване програмування з переходами до тверджень»?
Обломов

22
@ Nietzche-jou "це досить кричуща солом'яна людина [...]" Він саркастично використовує помилкову дихотомію, щоб показати, чому стигма нелогічна. Солом’яник - це логічна помилка, коли ви навмисно викривляєте позицію опонента, щоб було зрозуміло, що їх позиція легко перемагає. Наприклад, "Атеїсти просто атеїсти, тому що вони ненавидять Бога". Хибна дихотомія - це коли ви припускаєте, що протилежна крайність справжня, коли існує хоча б одна додаткова можливість (тобто чорно-біла). Наприклад, "Бог ненавидить гомосексуалістів, тому любить гетероси".
Бреден Кращий

27
@JLRishe Ти занадто глибоко читаєш те, чого навіть не існує. Це справжня логічна помилка, якщо людина, яка її чесно використовувала, вважає, що це має логічний сенс. Але він сказав це саркастично, чітко вказуючи на те, що він знає, що це смішно, і саме так він це демонструє. Він не "використовує це для виправдання своєї точки зору"; він використовує це, щоб показати, чому смішно говорити, що гото автоматично вводять код у спагетті. Тобто ти ще можеш писати лайний код без готів. В цьому його суть. А саркастична помилкова дихотомія - це його метод ілюструвати її жартівливим способом.
Бреден Кращий

32
-1 для того, щоб зробити дуже великий аргумент, чому зауваження Дейкстри не застосовується, при цьому забувши показати, які переваги gotoє насправді (про що йдеться у запитанні)
Maarten Bodewes

154

Дотримуватися найкращих практик сліпо - це не найкраща практика. Ідея уникати gotoтверджень як основної форми управління потоком полягає у тому, щоб уникнути створення нечитабельного коду спагетті. Якщо їх використовувати в потрібних місцях, вони іноді можуть бути найпростішим, зрозумілим способом висловлення ідеї. Вальтер Брайт, творець компілятора Zortech C ++ та мови програмування D, використовує їх часто, але розумно. Навіть із gotoзаявами його код все ще ідеально читабельний.

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


36

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

Правило з goto, яке ми використовуємо, полягає в тому, що для переходу вперед до однієї точки очищення виходу у функції переходить до goto.

Це правда - але лише якщо мова не дозволяє структурувати виключення з кодом очищення (наприклад, RAII або finally), що робить ту саму роботу краще (як це спеціально побудовано для цього), або коли є вагомі причини не використовувати структуровану обробку винятків (але у вас ніколи цього не буде, за винятком дуже низького рівня).

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

Крім цього, gotoце знак того, що недостатньо думок увійшло в конкретний фрагмент коду.


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

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


4
Тут просто цікаво, але як бути з випадком використання gotos для очищення коду. Під очищенням я маю на увазі не лише розмежування пам’яті, а й, скажімо, реєстрація помилок. Я читав купу публікацій, і, мабуть, ніхто не пише код, який друкує журнали .. хм ?!
шива

37
"В основному, через несправний характер гото (і я вважаю, що це суперечливо)" -1, і я перестав там читати.
o0 '.

14
Застереження проти goto походить із структуризованої епохи програмування, де раннє повернення також вважалося злом. Переміщення вкладених циклів у функцію торгує одним "злом" за іншим і створює функцію, яка не має реальної причини для існування (поза "це дозволило мені уникати використання goto!"). Це правда, що goto - це єдиний найпотужніший інструмент для створення коду спагетті, якщо він використовується без обмежень, але це ідеальне додаток для цього; це спрощує код. Кнут аргументував це використання, і Дайкстра сказав: "Не впадайте в пастку вірити, що я страшенно догматичний щодо гото".
Грязьова

5
finally? Тож використання винятків для речей, окрім помилок, добре, але використання gotoпогано? Я думаю, що винятки досить влучно названі.
Крістіан

3
@Mud: у випадках, з якими я стикаюся (щодня), найкращим рішенням є створення функції, яка не має реальних причин існувати . Причина: вона вилучає деталі з функції верхнього рівня, таким чином, щоб результат мав легко читати контрольний потік. Я особисто не стикаюся з ситуаціями, коли goto створює найбільш читабельний код. З іншого боку, я використовую кілька повернень у простих функціях, де хтось інший, можливо, віддасть перевагу одній точці виходу. Мені б хотілося побачити зустрічні приклади, коли goto читає, ніж рефакторинг на добре названі функції.
ToolmakerSteve

35

Ну, є одна річ, яка завжди гірша, ніж goto's; дивне використання інших операторів програмного забезпечення для уникнення переходу:

Приклади:

    // 1
    try{
      ...
      throw NoErrorException;
      ...
    } catch (const NoErrorException& noe){
      // This is the worst
    } 


    // 2
    do {
      ...break; 
      ...break;
    } while (false);


    // 3
    for(int i = 0;...) { 
      bool restartOuter = false;
      for (int j = 0;...) {
        if (...)
          restartOuter = true;
      if (restartOuter) {
        i = -1;
      }
    }

etc
etc

2
do{}while(false)Я думаю, це можна вважати ідіоматичним. Вам заборонено погоджуватися: D
Thomas Eding

37
@trinithis: Якщо це "ідіоматично", це лише через культ антигото. Якщо ви уважно подивитесь на це, то зрозумієте, що це просто спосіб сказати, goto after_do_block;не кажучи насправді про це. Інакше ... "петля", яка працює рівно один раз? Я б назвав це зловживанням контрольними структурами.
cHao

5
@ThomasEding Eding Є один виняток з вашої точки зору. Якщо ви коли-небудь робили якесь програмування на C / C ++ і вам довелося використовувати #define, ви знаєте, що {}, тоді як (0) є одним із стандартів для інкапсуляції декількох рядків коду. Наприклад: #define do {memcpy (a, b, 1); щось ++;} у той час як (0) безпечніше і краще, ніж #define memcpy (a, b, 1); щось ++
Ignas2526

10
@ Ignas2526 Ви просто чудово показали, як #defineце багато, у багато разів набагато гірше, ніж використання gotoраз у раз: D
Луань

3
+1 - хороший список способів, якими люди намагаються уникати готів, до крайності; Я бачив згадки про подібні методи в інших місцях, але це великий, стислий список. Замислюючись, чому за останні 15 років моїй команді ніколи не була потрібна жодна з цих технік, а також не використовувався готос. Навіть у програмі клієнт / сервер із сотнями тисяч рядків коду декількома програмістами. C #, java, PHP, python, javascript. Код клієнта, сервера та програми. Не хвастощі і не прозелітизація однієї позиції, просто по-справжньому цікаво, чому деякі люди стикаються з ситуаціями, які просять готос як найяскравіше рішення, а інші не ...
ToolmakerSteve

28

У C # операторі вимикання не допускається пропуск . Таким чином, goto використовується для передачі керування на конкретну мітку регістру комутатора або мітку за замовчуванням .

Наприклад:

switch(value)
{
  case 0:
    Console.Writeln("In case 0");
    goto case 1;
  case 1:
    Console.Writeln("In case 1");
    goto case 2;
  case 2:
    Console.Writeln("In case 2");
    goto default;
  default:
    Console.Writeln("In default");
    break;
}

Редагувати: Є одне виключення з правила "без пропуску". Прохідний доступ дозволений, якщо в заяві справи немає коду.



9
Тільки якщо в корпусі немає коду. Якщо у нього є код, ви повинні використовувати ключове слово goto.
Метью Уайт

26
Ця відповідь настільки смішна - C # вилучено, тому що багато хто вважає це шкідливим, і цей приклад використовує goto (також сприймається багатьма як шкідливий) для відродження оригінальної, нібито шкідливої ​​поведінки, Але загальний результат насправді менш шкідливий ( тому що з коду чітко видно, що провал є навмисним!).
thomasrutter

9
Просто тому, що ключове слово написане з літерами GOTO, це не робить гото. Цей представлений випадок не є гото. Це конструкція оператора перемикання для прориву. Знову ж таки, я дуже добре не знаю С #, тому можу помилитися.
Томас Едінг

1
Ну, в даному випадку це трохи більше, ніж провал (бо ви можете сказати, goto case 5:коли ви маєте випадок 1). Схоже, відповідь Конрада Рудольфа тут правильна: gotoце компенсація відсутньої функції (і менш зрозуміла, ніж справжня риса). Якщо те, що ми насправді хочемо, є прохідним, можливо, кращим за замовчуванням буде не пропуск, а щось на кшталт continueявного запиту.
Девід Стоун

14

#ifdef TONGUE_IN_CHEEK

Perl має таке, gotoщо дозволяє реалізовувати хвостові дзвінки бідолахи. :-P

sub factorial {
    my ($n, $acc) = (@_, 1);
    return $acc if $n < 1;
    @_ = ($n - 1, $acc * $n);
    goto &factorial;
}

#endif

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

(Цей самий коментар може стосуватися longjmpвинятків call/ccтощо), вони мають законне використання, але їх можна легко зловживати. Наприклад, викидання виключення, щоб уникнути глибоко вкладеної структури управління, за зовсім не виняткових обставин .)


Я думаю, що це єдина причина використовувати goto в Perl.
Бред Гілберт

12

За ці роки я написав більше декількох рядків мови складання. Зрештою, кожна мова високого рівня складається до gotos. Гаразд, назвіть їх "гілками" чи "стрибками" чи ще, але вони - готи. Чи може хто-небудь написати асемблер goto?

Тепер впевнений, що ви можете вказати програмісту Fortran, C або BASIC, що запускати бунт з готосом - це рецепт болонізу для спагетті. Однак відповідь не уникнути їх, а обережно їх використовувати.

Ніж можна використовувати для приготування їжі, звільнення когось або когось убити. Чи обходимося ми без ножів через страх останніх? Аналогічно goto: використовуючи недбало, це перешкоджає, ретельно використовувати це допомагає.


1
Можливо, ви хочете прочитати, чому я вважаю, що це принципово неправильно на сайті stackoverflow.com/questions/46586/…
Конрад Рудольф,

15
Кожен, хто просуває "все це зводиться до JMP все одно!" аргумент в основному не розуміє сенсу програмування мовою вищого рівня.
Ніцше-джоу

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

8

Погляньте, коли використовувати Goto при програмуванні на C :

Хоча використання goto майже завжди є поганою практикою програмування (напевно, ви можете знайти кращий спосіб виконання XYZ), бувають випадки, коли це дійсно не поганий вибір. Деякі можуть навіть стверджувати, що коли це корисно, це найкращий вибір.

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

Що я маю на увазі? У вас може бути якийсь код, який виглядає приблизно так:

int big_function()
{
    /* do some work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* clean up*/
    return [success];
}

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

Використовуючи goto, буде

int big_function()
{
    int ret_val = [success];
    /* do some work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
end:
    /* clean up*/
    return ret_val;
}

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

Більше того, оскільки goto використовується лише для переходу до однієї точки, це не так, якби ви створюєте масу коду спагетті, стрибаючи туди-сюди, намагаючись імітувати виклики функцій. Швидше, goto насправді допомагає писати більш структурований код.


Одним словом, gotoзавжди слід користуватися економно і в крайньому випадку - але для цього є час і місце. Питання має полягати не в тому, "чи потрібно вам його використовувати", а "чи це найкращий вибір", щоб ним користуватися.


7

Однією з причин goto є поганий, окрім стилю кодування є те, що ви можете використовувати його для створення перекриваються , але не вкладених циклів:

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

Це створило б химерну, але можливо юридичну структуру потоку контролю, де можлива послідовність типу (a, b, c, b, a, b, a, b, ...), що робить хакерів компілятора нещасними. Мабуть, існує ряд хитромудрих оптимізаційних хитрощів, які покладаються на те, що цей тип структури не виникає. (Я повинен перевірити свою копію книги дракона ...) Результатом цього може бути (використовуючи деякі компілятори), що інші оптимізації не робляться для коду, що містить gotos.

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


3
Ну ... повертає мене до днів програмування FORTRAN у фінансово-інформаційній компанії. У 2007 р.
Марцін

5
Будь-яку мовну структуру можна зловживати таким чином, щоб зробити її нечитабельною або неякісною.
ДАЙТЕ МОЕ правильне ДУМКА

@ JUST: Справа не в читанні чи поганій продуктивності, а в припущеннях та гарантіях щодо графіка потоку контролю. Будь-яке зловживання goto було б для підвищення продуктивності (або читабельності).
Андерс Евреній

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

1
... прямолінійний переклад необхідного алгоритму в код на основі GOTO може бути набагато простішим для перевірки, ніж безлад змінних прапорів та зловживаних циклівних конструкцій.
supercat

7

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

Для ілюстрації я наведу вам приклад, який ще ніхто тут не показав:

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

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

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    goto add;

// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
    goto add;

// Additional passes go here...

add:
// element is written to the hash table here

Тепер, якби я не використовував goto, як виглядатиме цей код?

Щось на зразок цього:

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    break;

if (add_index >= ELEMENTS_PER_BUCKET) {
  // Otherwise, find first empty element
  for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
    if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
      break;
  if (add_index >= ELEMENTS_PER_BUCKET)
   // Additional passes go here (nested further)...
}

// element is written to the hash table here

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

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


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

2
Ви насправді не пояснили, як додавання додаткових функцій позбавляється від goto, кількох рівнів відступу та помилкових, якщо заяви ...
Рікардо

Це виглядало б приблизно так, використовуючи стандартні позначення контейнерів: container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();я вважаю, що таке програмування набагато простіше читати. Кожна функція в цьому випадку може бути записана як чиста функція, про яку міркувати набагато простіше. Основна функція тепер пояснює, що код робить саме за назвою функцій, а потім, якщо ви хочете, ви можете подивитися на їх визначення, щоб дізнатися, як це робиться.
Девід Стоун

3
Той факт, що хтось повинен наводити приклади того, як певні алгоритми повинні природно використовувати гото - це сумне відображення того, як мало алгоритмічного мислення продовжується сьогодні !! Звичайно, приклад @ Рікардо - це (один із багатьох) ідеальних прикладів того, де гото є елегантним та очевидним.
Fattie

6

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


1
Я можу побачити щось подібне як корисне на такій мові, як C. Однак, коли ти маєш силу конструкторів / деструкторів C ++, це, як правило, не так корисно.
Девід Стоун

1
"У дійсно складних функціях ми розслабляємо це правило, щоб дозволити іншим стрибати вперед" Без прикладу, це звучить як, якщо ви робите складні функції ще складнішими за допомогою стрибків. Чи не був би "кращим" підхід рефактор і розділення цих складних функцій?
MikeMB

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

5

Найбільш продумане та ретельне обговорення тверджень goto, їх законного використання та альтернативних конструкцій, які можна використовувати замість "доброчесних заяв goto", але їх можна зловживати так само легко, як goto заяви, - це стаття Дональда Кнута " Структуроване програмування з goto Statements ". , в обчислювальних обстеженнях у грудні 1974 р. (том 6, № 4. С. 261 - 301).

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


3

У модулі Perl ви час від часу хочете створювати підпрограми або закриття на льоту. Річ у тім, що як тільки ви створили підпрограму, як до неї дістатися. Ви можете просто зателефонувати, але тоді, якщо підпрограма використовується, caller()вона не буде настільки корисною, як це могло б бути. Саме тут goto &subroutineваріація може бути корисною.

Ось короткий приклад:

sub AUTOLOAD{
  my($self) = @_;
  my $name = $AUTOLOAD;
  $name =~ s/.*:://;

  *{$name} = my($sub) = sub{
    # the body of the closure
  }

  goto $sub;

  # nothing after the goto will ever be executed.
}

Ви також можете використовувати цю форму gotoдля надання рудиментарної форми оптимізації хвостових викликів.

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

версії 16 Perl 5, яку краще записати як goto __SUB__;)

Є модуль, який імпортує tailмодифікатор, та модуль, який імпортуватиме, recurякщо вам не подобається використовувати цю форму goto.

use Sub::Call::Tail;
sub AUTOLOAD {
  ...
  tail &$sub( @_ );
}

use Sub::Call::Recur;
sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;
  recur( $n-1, $tally * $n );
}

Більшість інших причин використання gotoкраще виконувати з іншими ключовими словами.

Як redoі трохи коду:

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

Або переходимо до lastкоду з декількох місць:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

2

Якщо так, то чому?

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

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

libavcodec - чутливий до продуктивності фрагмент коду. Пряме вираження контрольного потоку, мабуть, є пріоритетним, оскільки воно, як правило, працює краще.


2

Так само, як ніхто ніколи не реалізовував заяву "ПРИЙДІТЬ ВІД" ....


8
Або в C ++, C #, Java, JS, Python, Ruby .... і т. Д. І т.д. тощо. Тільки вони називають це "винятками".
cHao

2

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

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

for (stepfailed=0 ; ! stepfailed ; /*empty*/)

Чи не повинно /*empty*/бути це stepfailed = 1? У будь-якому випадку, як це краще, ніж do{}while(0)? В обох вам потрібно breakвийти з нього (або у вашому stepfailed = 1; continue;). Мені здається непотрібним.
Томас Едінг

2

1) Найбільш поширене використання goto, про яке я знаю, - це емуляція обробки винятків на мовах, які не пропонують його, а саме в C. (Код, наведений Nuclear вище, саме такий.) Подивіться на вихідний код Linux, і ви ' побачимо використаний таким чином мільйон готів; За даними короткого опитування, проведеного у 2013 році, у коді Linux було близько 100 000 готів: http://blog.regehr.org/archives/894 . Використання Goto навіть згадується в посібнику зі стилю кодування Linux: https://www.kernel.org/doc/Documentation/CodingStyle . Як і об'єктно-орієнтоване програмування емулюється за допомогою структур, заповнених функціональними покажчиками, goto має своє місце в програмуванні на C. Отже, хто правий: Dijkstra чи Linus (і всі кодери ядра Linux)? Це в основному теорія проти практики.

Однак існує звичайна проблема, коли немає підтримки на рівні компілятора та перевірки на загальні конструкції / шаблони: простіше використовувати їх неправильно та вводити помилки без перевірки часу компіляції. Windows і Visual C ++, але в режимі C пропонують обробку винятків через SEH / VEH саме з цієї причини: винятки корисні навіть за межами мов OOP, тобто на процедурній мові. Але компілятор не завжди може зберегти ваш бекон, навіть якщо він пропонує синтаксичну підтримку винятків у мові. Розглянемо як приклад останнього випадку знамениту помилку Apple SSL "goto fail", яка просто копіювала один goto з катастрофічними наслідками ( https://www.imperialviolet.org/2014/02/22/applebug.html ):

if (something())
  goto fail;
  goto fail; // copypasta bug
printf("Never reached\n");
fail:
  // control jumps here

Ви можете мати абсолютно таку ж помилку, використовуючи винятки, підтримувані компілятором, наприклад у C ++:

struct Fail {};

try {
  if (something())
    throw Fail();
    throw Fail(); // copypasta bug
  printf("Never reached\n");
}
catch (Fail&) {
  // control jumps here
}

Але обох варіантів помилки можна уникнути, якщо компілятор аналізує та попереджає про недоступний код. Наприклад, компіляція з Visual C ++ на рівні попередження / W4 знаходить помилку в обох випадках. Наприклад, Java забороняє недоступний код (де його можна знайти!) З досить вагомої причини: це, ймовірно, помилка в середньому коді Джо. Поки конструкція goto не дозволяє цілям, які компілятор не може легко визначити, як-от gotos для обчислених адрес (**), компілятору не важче знайти недоступний код всередині функції з gotos, ніж використовувати Dijkstra -затверджений код.

(**) Зноска: готос до обчислених номерів рядків можливий у деяких версіях Basic, наприклад, GOTO 10 * x, де x - змінна. Досить заплутано, у Fortran "обчислений goto" посилається на конструкцію, еквівалентну оператору перемикання в C. Стандартний C не дозволяє обчислювати готи в мові, а лише gotos статично / синтаксично оголошені мітки. Однак GNU C має розширення, щоб отримати адресу мітки (оператор Unary, префікс &&), а також дозволяє перейти до змінної типу void *. Дивіться https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html для отримання додаткової інформації про цю неясну підтему. Решта цієї публікації не стосується цієї незрозумілої функції GNU C.

Стандартні C (тобто не обчислені) gotos зазвичай не є причиною того, що недоступний код не може бути знайдений під час компіляції. Звичайна причина - логічний код, як показано нижче. Дано

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

Компілятору так само важко знайти недосяжний код у будь-якій з наступних 3 конструкцій:

void tough1() {
  if (computation1() != computation2())
    printf("Unreachable\n");
}

void tough2() {
  if (computation1() == computation2())
    goto out;
  printf("Unreachable\n");
out:;
}

struct Out{};

void tough3() {
  try {
    if (computation1() == computation2())
      throw Out();
    printf("Unreachable\n");
  }
  catch (Out&) {
  }
}

(Вибачте мій стиль кодування, пов'язаний з дужками, але я намагався зберегти приклади максимально компактними.)

Visual C ++ / W4 (навіть з / Ox) не вдається знайти недоступний код у будь-якому з них, і, як ви, напевно, знаєте, проблема пошуку недосяжного коду взагалі не може бути вирішена. (Якщо ви мені не вірите в цьому: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )

Як пов'язаний з цим проблем, гото C може використовуватися для імітації винятків лише в тілі функції. Стандартна бібліотека C пропонує пару функцій setjmp () та longjmp () для емуляції нелокальних виходів / винятків, але вони мають деякі серйозні недоліки порівняно з іншими мовами. Стаття у Вікіпедії http://en.wikipedia.org/wiki/Setjmp.h досить добре пояснює цю останню проблему. Ця функційна пара також працює в Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), але навряд чи хтось їх використовує там, оскільки SEH / VEH є вищим. Навіть на Unix, я думаю, що setjmp та longjmp дуже рідко використовуються.

2) Я думаю, що другим найпоширенішим використанням goto в C є реалізація багаторівневої перерви або багаторівневого продовження, що також є досить неперервним випадком використання. Нагадаємо, що Java не дозволяє перейти до мітки goto, але дозволяє перервати мітку або продовжити мітку. За даними http://www.oracle.com/technetwork/java/simple-142616.html , це фактично найпоширеніший випадок використання готів у С (90% вони кажуть), але, на мій суб'єктивний досвід, системний код має тенденцію частіше використовувати gotos для обробки помилок. Можливо, в науковому коді або там, де ОС пропонує обробку виключень (Windows), тоді багаторівневі виходи є домінуючим випадком використання. Вони насправді не дають деталей щодо контексту свого опитування.

Відредаговано, щоб додати: виявляється, ці дві схеми використання знайдені в книзі С Керніган і Річі, приблизно на сторінці 60 (залежно від видання). Ще одне, що слід зазначити, що обидва випадки використання включають лише готи вперед. І виявляється, що MISRA C 2012 видання (на відміну від видання 2004 року) тепер дозволяє готос, якщо вони є лише форвардами.


Правильно. "Слон в кімнаті" полягає в тому, що ядро ​​Linux, на користь, як один із прикладів світової критичної бази коду - завантажується goto. Звичайно, так і є. Очевидно. "Анти-гото-мем" - просто цікавість з десятиліть тому. КУРСУ, в програмуванні є ряд речей (зокрема "статика", справді "глобальні" і, наприклад, "elseif"), якими можуть не зловживати непрофесіонали. Отже, якщо ваш двоюрідний брат є програмою learnin 2, ви їм скажете "ой не використовуй глобальних" та "ніколи не використовуй інше".
Fattie

Помилка goto fail не має нічого спільного з goto. Проблема викликана оператором if без дужок згодом. Практично про будь-яку копію заявки, вставленої двічі, це призвело б до проблеми. Я вважаю, що цей тип голих безшовних, якщо набагато шкідливіший, ніж гото.
muusbolla

2

Деякі кажуть, що немає жодних причин для goto на C ++. Деякі кажуть, що в 99% випадків є кращі альтернативи. Це не міркування, а лише нераціональні враження. Ось вагомий приклад, коли goto призводить до хорошого коду, щось на кшталт розширеного циклу "час роботи":

int i;

PROMPT_INSERT_NUMBER:
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    goto PROMPT_INSERT_NUMBER;          
  }

std::cout << "your number is " << i;

Порівняйте його з безкоштовним кодом:

int i;

bool loop;
do {
  loop = false;
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    loop = true;          
  }
} while(loop);

std::cout << "your number is " << i;

Я бачу ці відмінності:

  • вкладений {}блок потрібен (хоч і do {...} whileвиглядає більш звично)
  • loopпотрібна додаткова змінна, яка використовується в чотирьох місцях
  • потрібно більше часу, щоб прочитати та зрозуміти роботу з loop
  • loopне має яких - небудь даних, він просто контролює потік виконання, який є менш зрозумілим , ніж просто етикетки

Є ще один приклад

void sort(int* array, int length) {
SORT:
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    goto SORT; // it is very easy to understand this code, right?
  }
}

Тепер давайте позбудемося "зла" гото:

void sort(int* array, int length) {
  bool seemslegit;
  do {
    seemslegit = true;
    for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
      swap(data[i], data[i+1]);
      seemslegit = false;
    }
  } while(!seemslegit);
}

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

void sort(int* array, int length) {
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    i = -1; // it works, but WTF on the first glance
  }
}

Справа в тому, що Goto можна легко використати, але сам goto не винен. Зауважте, що мітка має функціональну область в C ++, тому вона не забруднює глобальну область застосування, як у чистому складі, в якій петлі , що перекриваються, мають своє місце і дуже поширені - як у наступному коді для 8051, де 7-сегментний дисплей підключений до P1. Програма замикає сегмент блискавки навколо:

; P1 states loops
; 11111110 <-
; 11111101  |
; 11111011  |
; 11110111  |
; 11101111  |
; 11011111  |
; |_________|

init_roll_state:
    MOV P1,#11111110b
    ACALL delay
next_roll_state:
    MOV A,P1
    RL A
    MOV P1,A
    ACALL delay
    JNB P1.5, init_roll_state
    SJMP next_roll_state

Є ще одна перевага: goto може служити названими петлями, умовами та іншими потоками:

if(valid) {
  do { // while(loop)

// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket

  } while(loop);
} // if(valid)

Або ви можете використовувати еквівалентний goto з відступом, тому вам не потрібен коментар, якщо ви вибираєте ім'я мітки:

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;

1

У Perl використовуйте мітку для "goto" з циклу - використовуючи операцію "last", яке схоже на розрив.

Це дозволяє краще контролювати вкладені петлі.

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


Я думаю, що єдиною формою гото, яку ви коли-небудь використовуватимете в Perl, є goto &subroutine. Яка запускає підпрограму з поточної @_, замінюючи поточну підпрограму в стеці.
Бред Гілберт

1

Проблема з "goto" та найважливішим аргументом руху "goto-programming '" полягає в тому, що якщо ви використовуєте його занадто часто, ваш код, хоча він може вести себе правильно, стає нечитабельним, незбагненним, не переглядається тощо. У 99,99% випадки 'goto' призводять до коду спагетті. Особисто я не можу придумати жодної вагомої причини щодо того, чому б я використовував "goto".


11
Говорячи, що "я не можу придумати жодної причини" у вашому аргументі, формально, це en.wikipedia.org/wiki/Argument_from_ignorance , хоча я вважаю за краще термінологію "доказ відсутністю уяви".
ДАЙТЕ МОЕ правильне ДУМКА

2
@ ПРАВИЛЬНА МОЯ ПРАВИЛЬНА ДУМКА: Це логічна помилка лише в тому випадку, якщо вона використовується на посаді. З точки зору дизайнера мови, це може бути вагомим аргументом для зважування вартості включення функції ( goto). @ використання cschol схоже: Хоча, можливо, не розробляє мову зараз, він в основному оцінює зусилля дизайнера.
Конрад Рудольф

1
@KonradRudolph: IMHO, якщо мова дозволяє, gotoза винятком контекстів, де це може призвести до існування змінних, може бути дешевше, ніж намагатися підтримувати будь-який тип керуючої структури, кому це може знадобитися. Написання коду gotoможе бути не настільки приємним, як використання якоїсь іншої структури, але можливість написання такого коду gotoдопоможе уникнути «дірок у виразності» - конструкцій, для яких мова не здатна написати ефективний код.
supercat

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

1
@thb Так, звичайно. Часто це робить код набагато важчим для читання та обґрунтування. Практично кожного разу, коли хтось публікує код, що міститься gotoна сайті з переглядом коду, усунення gotoзначно спрощує логіку коду.
Конрад Рудольф

1

Звичайно, GOTO можна використовувати, але є ще одна важлива річ, ніж стиль коду, або якщо код є читливим чи не читабельним, що ви повинні мати на увазі, коли ви його використовуєте: код всередині може бути не таким надійним, як ви думати .

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

If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)

Еквівалентний код з GOTO

If A == 0 Then GOTO FINAL EndIf
   A = 0
FINAL:
Write("Value of A:" + A)

Перше, що ми думаємо, це те, що результатом обох бітів коду буде те, що "Значення A: 0" (звичайно, ми припускаємо виконання без паралелізму)

Це не правильно: у першому зразку A завжди буде 0, але у другому зразку (із заявою GOTO) A може бути не 0. Чому?

Причина полягає в тому, що з іншого пункту програми я можу вставити а, GOTO FINALне контролюючи значення A.

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

Матеріали, пов'язані з цим матеріалом, можна знайти у відомій статті пана Дійкстри "Справа проти заяви GO TO"


6
Це часто траплялося в старошкільній ОСНОВІ. Однак у сучасних варіантах вам заборонено стрибати в середину іншої функції ... або в багатьох випадках навіть минулого оголошення змінної. В основному (каламбур не призначений), сучасні мови значною мірою покінчили з "нестримними" ГОТАми, про які говорив Дайкстра ... і єдиний спосіб використовувати це, як він сперечався, - це вчинення певних інших грізних гріхів. :)
cHao

1

Я використовую goto в наступному випадку: коли потрібно повернутися з функцій в різних місцях, і перед поверненням потрібно зробити деякі неініціалізації:

не-goto версія:

int doSomething (struct my_complicated_stuff *ctx)    
{
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
        db_disconnect(conn);
        return -1;      
        }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        free(temp_data);    
        db_disconnect(conn);    
        return -2;
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        pthread_mutex_unlock(ctx->mutex);
        free(temp_data);
        db_disconnect(conn);        
        return -3;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -4;  
    }

    if (ctx->something_else->additional_check) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -5;  
    }


    pthread_mutex_unlock(ctx->mutex);
    free(temp_data);    
    db_disconnect(conn);    
    return 0;     
}

goto версія:

int doSomething_goto (struct my_complicated_stuff *ctx)
{
    int ret=0;
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
            ret=-1;
           goto exit_db;   
          }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        ret=-2;
        goto exit_freetmp;      
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        ret=-3;
        goto exit;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
        ret=-4;
        goto exit_freekey; 
    }

    if (ctx->something_else->additional_check) {
        ret=-5;
        goto exit_freekey;  
    }

exit_freekey:
    rsa_free(key);
exit:    
    pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
    free(temp_data);        
exit_db:
    db_disconnect(conn);    
    return ret;     
}

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


3
Ось чому у нас є finallyблоки в C #
Джон Сондерс

^ як сказав @JohnSaunders. Це приклад "використання goto, оскільки в мові немає відповідної конструкції управління". ЯКЩО кодовий запах вимагає МІЛЬКОГО пункту переходу поблизу повернення. Є стиль програмування, який є більш безпечним (простіше не накручувати) І не вимагає готів, що працює чудово навіть на мовах без "остаточного": спроектуйте ці "очищення" дзвінки так, щоб їх було нешкідливо робити, коли немає чистих вгору потрібно. Сформулюйте все, окрім очищення, на метод, який використовує конструкцію з множинним поверненням. Зателефонуйте за цим методом, а потім виконайте очищення.
ToolmakerSteve

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

@ToolmakerSteve це не кодовий запах; насправді це надзвичайно поширена закономірність у C і вважається одним із найбільш вагомих способів використання goto. Ви хочете, щоб я створив 5 методів, у кожного з яких є власний непотрібний тест, просто для обробки очищення від цієї функції? Тепер ви створили код і ефективність роботи. Або ви могли просто скористатися goto.
muusbolla

@muusbolla - 1) "створити 5 методів" - Ні. Я пропоную єдиний метод, який очищає будь-які ресурси, які мають ненулеве значення. АБО бачите мою альтернативу, яка використовує gotos, що всі йдуть до тієї ж точки виходу, яка має ту саму логіку (що вимагає додаткової кількості, якщо на ресурс, як ви кажете). Але не зауважте, що при використанні Cви праві - незалежно від причини коду в C, його майже напевно компроміс, який сприяє найбільш «прямому» коду. (Моя пропозиція ручки складних ситуацій , в яких можуть бути або не були виділені будь-який даний ресурс Але так, надмірністю в цьому випадку.)
ToolmakerSteve

0

Едсгер Дайкстра, комп'ютерний вчений, який мав великий внесок у цю сферу, також був відомий тим, що критикував використання GoTo. У Вікіпедії є коротка стаття про його аргумент .


0

Це зручно час від часу обробляти рядки символами.

Уявіть щось на зразок цього printf-esque прикладу:

for cur_char, next_char in sliding_window(input_string) {
    if cur_char == '%' {
        if next_char == '%' {
            cur_char_index += 1
            goto handle_literal
        }
        # Some additional logic
        if chars_should_be_handled_literally() {
            goto handle_literal
        }
        # Handle the format
    }
    # some other control characters
    else {
      handle_literal:
        # Complicated logic here
        # Maybe it's writing to an array for some OpenGL calls later or something,
        # all while modifying a bunch of local variables declared outside the loop
    }
}

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.