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

В то время как (1).. перерыв вместо goto

Я нашел следующий код в программе на C:

while (1)
{
    do_something();
    if (was_an_error()) break;

     do_something_else();
     if (was_an_error()) break;

     [...]

     break;
}
[cleanup code]

Здесь while(1) используется как локальная эмуляция "finally". Вы также можете записать это с помощью goto s:

do_something()
if (was_an_error()) goto out;

do_something_else()
if (was_an_error()) goto out;

[...]
out:
[cleanup code]

Я думал, что решение goto - обычная идиома. Я видел несколько случаев этой идиомы в источниках ядра, и это также упоминается в книге "Кодекс Чтения" Диомидиса Спинеллиса.

Мой вопрос: какое решение лучше? Есть ли какая-либо конкретная причина для использования решения while(1)?

Вопрос 943826 не отвечает на мой вопрос.

4b9b3361

Ответ 1

По-видимому, универсальный регресс для GOTO во многом связан с письмом Эдсгера Дейкстры "Перейти к заявлению, который считается вредным".

Если вы решили не использовать goto, что-то вроде

do {
    ...
while(0);

вероятно, безопаснее, чем while (1) {...}, так как он гарантирует, что вы не будете случайно зацикливаться (и если вы случайно не зацикливаете, то с (1) вы, вероятно, непреднамеренно зацикливаетесь бесконечно).

Единственное преимущество, заключающееся в том, что (ab), использующее do/break/while или while/break для этой цели, имеет более высокую, заключается в том, что вам гарантировано не прыгать над конструкцией - goto можно использовать для перехода на метку раньше в пределах одной и той же функции.

Недостаток, который выполняет /break/while и т.д., имеет преимущество в том, что вы ограничены одной точкой выхода (сразу после цикла). В некоторых случаях вам может потребоваться поэтапная очистка: например, когда вы открываете дескриптор файла, malloc некоторая память, читайте из файла... если чтение не выполняется, вам нужно очистить malloc. Если malloc выходит из строя, вам не нужно его очищать, но вам все равно нужно очистить дескриптор файла. С goto вы можете иметь одну метку на этапе очистки и прыгать точно в нужную точку в зависимости от того, где произошла ваша ошибка.

По моему мнению, слепо избегать GOTO из-за распространенной ненависти к нему более вредно, чем тщательное обоснование дела для его использования в каждом конкретном случае. Я использую правило: "делает ли ядро ​​Linux это? Если это так, это не может быть так плохо". Замените Linux-ядро любым другим хорошим примером современной разработки программного обеспечения.

Ответ 2

Ввод кода в отдельную функцию, а использование return для выхода на раннем этапе - это еще один способ сделать это, с учетом простой интеграции кода возврата, указывающего природу сбоя.

Ответ 3

Хотя использование goto обычно обескураживает, некоторые редкие ситуации, подобные вашим, - это место, где лучшие практики не самые лучшие.

Итак, если goto делает самый ясный код, я бы его использовал. использование цикла while (true) для эмуляции goto является чем-то неестественным. То, что вам действительно нужно, это goto!

Ответ 4

Я знаю, что мой стиль не самый крутой, но я предпочитаю его, потому что он не нуждается в каких-либо специальных конструкциях и является кратким и не слишком сложным для понимания:

error = (!error) && do_something1();
error = (!error) && do_something2();
error = (!error) && do_something3();

// Cleanup code

Ответ 5

Почему бы не использовать серию операторов if? Я обычно пишу это так, поскольку я нахожу это намного яснее, чем цикл:

bool ok = true;

do_something();
if (was_an_error()) ok = false;

if (ok)
{
    do_something_else();
    if (was_an_error()) ok = false;
}

if (ok)
{
    do_something_else_again();
    if (was_an_error()) ok = false;
}

[...]

[Cleanup code]

Также, если вы работаете с строгими стандартами кодирования, да goto, вероятно, будет запрещен, но часто это так: break и continue, поэтому цикл не обязательно является обходным путем для этого.

Ответ 6

"break" понимает семантику области блока, в то время как "goto" не обращает на это внимания. Другими словами, "while-break" может быть переведен на функциональные языки, такие как Lisp с хвостовой рекурсией, "goto" не может.

Ответ 7

Обычно GOTO считаются плохими, но в некоторых местах, где есть только переходы вперед через GOTO, они не так уж плохи. Люди избегают GOTO как чумы, но хорошо продуманное использование GOTO иногда является лучшим решением IMHO.

Ответ 8

Я думаю, что это использование (для управления ресурсами) goto в порядке.

Ответ 9

Используйте его, если вы не можете использовать goto по какой-либо причине

  • запрещено в ваших проектных соглашениях
  • запрещено вашим инструментом lint

Я также думаю, что это также один из случаев, когда макросы не являются злыми:

#define DO_ONCE for (int _once_dummy = 0; _once_dummy < 1; _once_dummy++)

Ответ 10

Мне нравится подход while (1). Я использую его сам. В частности, когда цикл может повторяться, продолжая, например, когда элемент обрабатывается внутри такого цикла и выполняется в нескольких подходах.

Ответ 11

Никогда не используйте цикл условий с постоянно действующим условием. Поскольку условие всегда верно, зачем использовать условный цикл?

Постоянно истинные условия наиболее непосредственно представлены goto.

Ответ 12

При использовании "goto" для ситуаций обработки ошибок довольно часто, я все же предпочитаю "пока" решение (или "делать пока" ). В случае с "goto" гораздо меньше вещей, которые может гарантировать компилятор. Если вы сделаете опечатку в названии метки, компилятор не сможет вам помочь. Если кто-то использует другой goto на другой ярлык в этом блоке, есть хорошая вероятность, что код очистки не будет вызван. Когда вы используете более структурированные конструкции управления потоком, вам всегда гарантируется, какой код будет работать после завершения цикла.

Ответ 13

"do while" и "goto out" различаются в этой области:

1. Локальная инициализация переменных

void foo(bool t = false)
{
    if (t)
    {
        goto DONE;
    }

    int a = 10; // error : Goto bypass local variable initialization 

    cout << "a=" << a << "\n";
DONE:
}

Хорошо инициализировать локальные локальные переменные in do... while (0).

void bar(bool t = false)
{
    do{
        if (t)
        {
            break; 
        }

        int a = 10;  // fine

        cout << "a=" << a << "\n";
    } while (0);

}

2 разница для макросов. "делать пока" немного лучше. "goto DONE" в макроре не так. Если код выхода более сложный, сделайте следующее:

err = some_func(...);
if (err)
{
    register_err(err, __LINE__, __FUNC__);
#if defined (_DEBUG)
    do_some_debug(err)
#endif
    break;
}

и вы пишете этот код снова и снова, вы, вероятно, поместите их в макрос.

#define QUIT_IF(err)                     \
if (err)                                       \
{                                              \
    register_err(err, __LINE__, __FUNC__);     \
    DO_SOME_DEBUG(err)                         \
    break; // awful to put break in macro, but even worse to put "goto DONE" in macro.  \
}

И код станет:

do
{
    initial();

    do 
    {
        err = do_step1();
        QUIT_IF(err);

        err = do_step2();
        QUIT_IF(err);

        err = do_step3();
        QUIT_IF(err);

        ....
    } while (0);
    if (err) {     // harder for "goto DONE" to get here while still using macro.
        err = do_something_else();
    }
    QUIT_IF(err);
    .....
} while (0);

3.do... while (0) обрабатывает разные уровни выхода с одним и тем же макросом. Код показан выше. goto... это не так для Macro, потому что вам нужны разные ярлыки для разных уровней.

Говоря это, я не люблю их обоих. Я бы предпочел использовать метод исключения. Если исключение не разрешено, я использую "do... while (0)", так как весь блок имеет отступ, на самом деле его легче читать, чем стиль "goto DONE".