1) Наиболее распространенное использование goto, о котором я знаю, это эмуляция обработки исключений в языках, которые этого не предлагают, а именно в C. (Код, приведенный выше в Nuclear, является именно этим.) Посмотрите на исходный код Linux и вы увидите, что таким образом использовался базилик гото; согласно короткому опросу, проведенному в 2013 году, в коде Linux было около 100 000 goto: http://blog.regehr.org/archives/894. Использование Goto даже упоминается в руководстве по стилю кодирования Linux: https://www.kernel.org/doc/Documentation/CodingStyle. Точно так же, как объектно-ориентированное программирование эмулируется с использованием структуры, заполненные указателями функций, goto имеет свое место в программировании на Си. Так кто же прав: Дейкстра или Линус (и все кодировщики ядра Linux)? Это теория против практики в принципе.
Однако существует обычная хитрость, связанная с отсутствием поддержки на уровне компилятора и проверками общих конструкций / шаблонов: проще использовать их неправильно и вводить ошибки без проверок во время компиляции. Windows и Visual C ++, но в режиме C предлагают обработку исключений через SEH / VEH по этой самой причине: исключения полезны даже вне языков ООП, то есть на процедурном языке. Но компилятор не всегда может сохранить ваш бекон, даже если он предлагает синтаксическую поддержку исключений в языке. Рассмотрим в качестве примера последнего случая известную ошибку 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 - переменная. Весьма странно, что в Фортране «computed goto» относится к конструкции, которая эквивалентна выражению switch в C. Стандартный C не допускает вычисляемые goto в языке, а только goto для статически / синтаксически объявленных меток. GNU C, однако, имеет расширение для получения адреса метки (унарный, префикс && оператор), а также позволяет перейти к переменной типа 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 goto может использоваться для эмуляции исключений только внутри тела функции. Стандартная библиотека C предлагает пару функций setjmp () и longjmp () для эмуляции нелокальных выходов / исключений, но они имеют ряд серьезных недостатков по сравнению с другими языками. Статья Wikipedia 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, это на самом деле самый распространенный вариант использования gotos в C (90% говорят), но в моем субъективном опыте системный код имеет тенденцию чаще использовать gotos для обработки ошибок. Возможно, в научном коде или там, где ОС предлагает обработку исключений (Windows), многоуровневые выходы являются доминирующим вариантом использования. Они не дают никаких подробностей относительно контекста своего опроса.
Отредактировано, чтобы добавить: оказывается, что эти два образца использования находятся в книге C Кернигана и Ричи, около страницы 60 (в зависимости от издания). Также следует отметить, что в обоих случаях используются только прямые переходы. И оказывается, что выпуск MISRA C 2012 (в отличие от выпуска 2004 г.) теперь разрешает переходы, если они только передовые.