Почему некоторые программисты ядра используют goto вместо простых циклов?
Когда я узнал C, учитель сказал мне весь день: "Не используйте goto, это плохая привычка, это уродливо, это опасно!" и т.д.
Почему тогда некоторые программисты ядра используют goto
, например в этой функции, где его можно заменить простым
while(condition) {}
или
do {} while(condition);
Я не понимаю этого. В некоторых случаях лучше использовать goto вместо while
/do
- while
? И если да, то почему?
Ответы
Ответ 1
В этом примере я подозреваю, что речь идет о дооснащении поддержки SMP в коде, который был первоначально написан безопасным способом, не поддерживающим SMP. Добавление пути goto again;
намного проще и менее инвазивно, чем реструктурировать функцию.
Я не могу сказать, что мне нравится этот стиль много, но я также думаю, что он ошибочно избегал goto
по идеологическим соображениям. Одним из особых случаев использования goto
(отличного от этого примера) является то, что goto
используется только для перемещения вперед в функции, а не назад. Этот класс использования никогда не приводит к конструкциям цикла, возникающим из goto
, и это почти всегда самый простой и ясный способ реализовать необходимое поведение (которое обычно очищается и возвращается при ошибке).
Ответ 2
Исторический контекст:. Мы должны помнить, что Дейкстра писал Goto Wased Harmful в 1968 году, когда многие программисты использовали goto
в качестве замены структурированного программирования (if
, while
, for
и т.д.).
Это 44 года спустя, и редко можно найти это использование goto
в дикой природе. Структурированное программирование уже давно выиграло.
Анализ случая:
Пример кода выглядит следующим образом:
SETUP...
again:
COMPUTE SOME VALUES...
if (cmpxchg64(ptr, old_val, val) != old_val)
goto again;
Структурированная версия выглядит следующим образом:
SETUP...
do {
COMPUTE SOME VALUES...
} while (cmpxchg64(ptr, old_val, val) != old_val);
Когда я смотрю на структурированную версию, я сразу же думаю: "Это цикл". Когда я смотрю версию goto
, я думаю об этом как о прямой линии с аргументом "попробуйте еще раз" в конце.
Версия goto
имеет как SETUP
, так и COMPUTE SOME VALUES
в том же столбце, что подчеркивает, что большую часть времени поток управления проходит через оба. Структурированная версия помещает SETUP
и COMPUTE SOME VALUES
в разные столбцы, что подчеркивает, что управление может проходить через них по-разному.
Вопрос в том, какой акцент вы хотите поместить в код? Вы можете сравнить это с goto
для обработки ошибок:
Структурированная версия:
if (do_something() != ERR) {
if (do_something2() != ERR) {
if (do_something3() != ERR) {
if (do_something4() != ERR) {
...
Перейти к версии:
if (do_something() == ERR) // Straight line
goto error; // |
if (do_something2() == ERR) // |
goto error; // |
if (do_something3() == ERR) // |
goto error; // V
if (do_something4() == ERR) // emphasizes normal control flow
goto error;
Сгенерированный код в основном тот же, поэтому мы можем рассматривать его как типографскую проблему, например отступы.
Ответ 3
Очень хороший вопрос, и я думаю, что только автор может дать окончательный ответ. Я добавлю свои предположения, сказав, что это могло начаться с использования его для обработки ошибок, как объяснено @Izkata, и ворота были открыты для использования его и для базовых циклов.
На мой взгляд, использование обработки ошибок является законным в системном программировании. Функция постепенно выделяет память, как она прогрессирует, и если обнаружена ошибка, она будет goto
соответствующий ярлык, чтобы освободить ресурсы в обратном порядке от этой точки.
Таким образом, если ошибка возникает после первого размещения, она переходит к последней метке ошибки, чтобы освободить только один ресурс. Аналогично, если ошибка возникает после последнего выделения, она переходит к первой метке ошибки и запускается оттуда, освобождая все ресурсы до конца функции. Такой шаблон для обработки ошибок по-прежнему необходимо использовать осторожно, особенно когда настоятельно рекомендуется модифицировать код, valgrind и модульные тесты. Но это, возможно, более читабельно и ремонтопригодно, чем альтернативные подходы.
Одно из золотых правил использования goto
- избегать так называемого кода спагетти. Попробуйте нарисовать линии между каждым goto
и соответствующим ярлыком. Если у вас пересечение линий, вы пересекли линию :). Такое использование goto
очень трудно читать и является распространенным источником трудных для отслеживания ошибок, поскольку они встречаются в таких языках, как BASIC, которые используют его для управления потоком.
Если вы сделаете только один простой цикл, вы не получите пересечения линий, поэтому он все еще читабелен и удобен в обслуживании, становясь во многом вопросом стиля. Тем не менее, поскольку их можно так же легко сделать с помощью ключевых слов цикла, предоставляемых языком, как вы указали в своем вопросе, я по-прежнему рекомендую избегать использования циклов goto
for, просто потому, что конструкции for
, do/while
или while
более элегантны по дизайну.