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 - переменная. Весьма странно, что в Фортране «вычисляемое goto» относится к конструкции, которая эквивалентна выражению switch в C. Стандарт C не допускает вычисляемые goto в языке, а только goto для статически / синтаксически объявленных меток. GNU C, однако, имеет расширение для получения адреса метки (унарный оператор, префикс && оператор), а также позволяет перейти к переменной типа void *. См. Https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html для получения дополнительной информации по этой неясной подтеме. Остальная часть этого поста не связана с этой неясной функцией GNU C.
Стандартные C (то есть не вычисляемые) goto обычно не являются причиной, по которой недоступный код не может быть найден во время компиляции. Обычная причина - логический код, подобный следующему. Дано
int computation1() {
return 1;
}
int computation2() {
return computation1();
}
Компилятору так же трудно найти недоступный код в любой из следующих трех конструкций:
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 () для эмуляции нелокальных выходов / исключений, но они имеют ряд серьезных недостатков по сравнению с другими языками. Статья в Википедии 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 г.) теперь разрешает goto, если они только передовые.