Подтвердить что ты не робот

Почему некоторые программисты ядра используют goto вместо простых циклов?

Когда я узнал C, учитель сказал мне весь день: "Не используйте goto, это плохая привычка, это уродливо, это опасно!" и т.д.

Почему тогда некоторые программисты ядра используют goto, например в этой функции, где его можно заменить простым

while(condition) {} 

или

do {} while(condition);

Я не понимаю этого. В некоторых случаях лучше использовать goto вместо while/do - while? И если да, то почему?

4b9b3361

Ответ 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 более элегантны по дизайну.