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 року) тепер дозволяє готос, якщо вони є лише форвардами.