Бесконечный цикл в C/С++

Есть несколько возможностей сделать бесконечный цикл, вот несколько вариантов:

  • for(;;) {}
  • while(1) {}/while(true) {}
  • do {} while(1)/do {} while(true)

Существует ли определенная форма, которую нужно выбрать? И делают ли современные компиляторы разницу между средним и последним выражением или понимают, что это бесконечный цикл и полностью пропускает проверяющую часть?

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

Edit2: Я сделал несколько grep для последних версий, взятых из kernel.org. Кажется, я ничего не изменил с течением времени (по крайней мере, в пределах ядра) введите описание изображения здесь

Ответ 1

Проблема с заданием этого вопроса заключается в том, что вы получите так много субъективных ответов, что просто укажите "Я предпочитаю это...". Вместо того, чтобы делать такие бессмысленные заявления, я постараюсь ответить на этот вопрос фактами и ссылками, а не на личные мнения.

По опыту мы, вероятно, можем начать с исключения альтернатив do-while (и goto), поскольку они обычно не используются. Я не могу вспомнить, когда-либо видел их в живом коде, написанном профессионалами.

while(1), while(true) и for(;;) - это 3 разные версии, обычно существующие в реальном коде. Они, конечно, полностью эквивалентны и приводят к одному и тому же машинным кодам.


for(;;)

  • Это оригинальный, канонический пример вечного цикла. В древней C Библии Язык программирования C от Kernighan и Ritchie мы можем прочитать, что:

    K & R 2nd ed 3.5:

    for (;;) {
    ...
    }
    

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

    Долгое время (но не навсегда) эта книга рассматривалась как канон и само определение языка С. Поскольку K & R решил показать пример for(;;), это было бы считаться наиболее правильной формой, по крайней мере, до стандартизации C в 1990 году.

    Однако сами K & R уже заявили, что это предпочтение.

    И сегодня K & R является очень сомнительным источником для использования в качестве канонической ссылки на C. Он не только устарел несколько раз (не рассматривая C99, ни C11), он также проповедует практики программирования, которые часто считаются плохими или откровенно опасными в современном программировании на С.

    Но, несмотря на то, что K & R является сомнительным источником, этот исторический аспект представляется самым сильным аргументом в пользу for(;;).

  • Аргумент против цикла for(;;) заключается в том, что он несколько неясен и нечитабель. Чтобы понять, что делает код, вы должны знать следующее правило из стандарта:

    ISO 9899: 2011 6.8.5.3:

    for ( clause-1 ; expression-2 ; expression-3 ) statement
    

    /-/

    Оба предложения-1 и выражение-3 могут быть опущены. Пропущенное выражение-2 заменяется на ненулевую константу.

    Основываясь на этом тексте из стандарта, я думаю, что большинство согласится с тем, что оно не только неясно, но и тонко, так как 1-я и 3-я части цикла for обрабатываются по-другому, чем 2-е, когда они опущены.


while(1)

  • Это, предположительно, более читаемая форма, чем for(;;). Однако он опирается на другое неясное, хотя и известное правило, а именно, что C рассматривает все ненулевые выражения как логические логические истины. Каждый программист C знает об этом, поэтому вряд ли это большая проблема.

  • Существует одна большая практическая проблема с этой формой, а именно, что компиляторы обычно предупреждают об этом: "условие всегда истинно" или аналогично. Это хорошее предупреждение, которое вы действительно не хотите отключать, потому что оно полезно для поиска различных ошибок. Например, ошибка, например while(i = 1), когда программист намеревался написать while(i == 1).

    Кроме того, внешние статические анализаторы кода могут скулить о том, что "условие всегда истинно" .


while(true)

  • Чтобы сделать while(1) еще более удобочитаемым, используйте вместо него while(true). Консенсус среди программистов, по-видимому, заключается в том, что это наиболее читаемая форма.

  • Однако эта форма имеет ту же проблему, что и while(1), как описано выше: предупреждения "условие всегда истинно" .

  • Когда дело доходит до C, эта форма имеет еще один недостаток, а именно, что она использует макрос true из stdbool.h. Поэтому, чтобы выполнить эту компиляцию, нам нужно включить заголовочный файл, который может быть или не быть неудобным. В С++ это не проблема, поскольку bool существует как примитивный тип данных, а true - это ключевое слово языка.

  • Еще один недостаток этой формы заключается в том, что он использует тип b99 типа C99, который доступен только на современных компиляторах, а не обратно совместим. Опять же, это только проблема в C, а не в С++.


Итак, какую форму использовать? Ничего не кажется идеальным. Это, как и K & R, уже сказал в темные века, вопрос личных предпочтений.

Лично я всегда использую for(;;), чтобы избежать предупреждений компилятора/анализатора, часто генерируемых другими формами. Но, возможно, более важно из-за этого:

Если даже начинающий C знает, что for(;;) означает вечный цикл, то кто вы пытаетесь сделать код более читаемым?

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

И так как каждый, кто должен читать ваш код, уже знает, что означает for(;;), нет смысла делать его более читаемым - он уже доступен для чтения.

Ответ 2

Это очень субъективно. Я пишу это:

while(true) {} //in C++

Потому что его намерение очень ясно, и оно также читаемо: вы смотрите на него, и вы знаете, что бесконечный цикл предназначен.

Можно сказать, что for(;;) также ясно. Но я бы сказал, что из-за его сложного синтаксиса этот параметр требует дополнительных знаний, чтобы прийти к выводу, что это бесконечный цикл, поэтому он относительно менее ясен. Я бы даже сказал, что есть больше программистов, которые не знают, что делает for(;;) (даже если они знают обычный цикл for), но почти все программисты, которые знают цикл while, сразу выяснят, что while(true) делает.

Для меня пишется for(;;) как бесконечный цикл, как запись while() для обозначения бесконечного цикла — в то время как первое работает, последнее НЕ. В первом случае пустое условие оказывается true неявно, но в последнем случае это ошибка! Мне лично это не понравилось.

Теперь while(1) также присутствует в конкурсе. Я бы спросил: почему while(1)? Почему бы не while(2), while(3) или while(0.1)? Ну, что бы вы ни пишете, вы на самом деле имеете в виду while(true) — если да, то почему бы не написать его вместо этого?

В C (если я когда-либо пишу), я бы, вероятно, написал бы это:

while(1) {} //in C

В то время как while(2), while(3) и while(0.1) будут иметь смысл. Но для того, чтобы быть совместимым с другими программистами на С, я писал бы while(1), потому что многие программисты C пишут это, и я не нахожу причин отклоняться от нормы.

Ответ 3

В конечном итоге скука, я на самом деле написал несколько версий этих циклов и скомпилировал его с GCC на моем mac mini.

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

heres для while/for loop

    .section    __TEXT,__text,regular,pure_instructions
    .globl  _main
    .align  4, 0x90
_main:                                  ## @main
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp2:
    .cfi_def_cfa_offset 16
Ltmp3:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp4:
    .cfi_def_cfa_register %rbp
    movl    $0, -4(%rbp)
LBB0_1:                                 ## =>This Inner Loop Header: Depth=1
    jmp LBB0_1
    .cfi_endproc

и цикл while while

        .section    __TEXT,__text,regular,pure_instructions
    .globl  _main
    .align  4, 0x90
_main:                                  ## @main
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp2:
    .cfi_def_cfa_offset 16
Ltmp3:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp4:
    .cfi_def_cfa_register %rbp
    movl    $0, -4(%rbp)
LBB0_1:                                 ## =>This Inner Loop Header: Depth=1
    jmp LBB0_2
LBB0_2:                                 ##   in Loop: Header=BB0_1 Depth=1
    movb    $1, %al
    testb   $1, %al
    jne LBB0_1
    jmp LBB0_3
LBB0_3:
    movl    $0, %eax
    popq    %rbp
    ret
    .cfi_endproc

Ответ 4

Кажется, что нравится while (true):

fooobar.com/questions/18154/...

fooobar.com/questions/18347/...

fooobar.com/questions/18347/...

fooobar.com/questions/18347/...

fooobar.com/questions/18347/...

В соответствии с SLaks они скомпилируются одинаково.

Бен Зотто также говорит, что это неважно:

Это не быстрее. Если вам действительно все равно, скомпилируйте его с помощью ассемблера для своей платформы и посмотрите. Это не имеет значения. Это никогда не имеет значения. Напишите свои бесконечные петли, как вам нравится.

В ответ на user1216838, здесь моя попытка воспроизвести его результаты.

Здесь моя машина:

cat /etc/*-release
CentOS release 6.4 (Final)

Версия gcc:

Target: x86_64-unknown-linux-gnu
Thread model: posix
gcc version 4.8.2 (GCC)

И тестовые файлы:

// testing.cpp
#include <iostream>

int main() {
    do { break; } while(1);
}

// testing2.cpp
#include <iostream>

int main() {
    while(1) { break; }
}

// testing3.cpp
#include <iostream>

int main() {
    while(true) { break; }
}

Команды:

gcc -S -o test1.asm testing.cpp
gcc -S -o test2.asm testing2.cpp
gcc -S -o test3.asm testing3.cpp

cmp test1.asm test2.asm

Единственная разница - это первая строка, также известная как имя файла.

test1.asm test2.asm differ: byte 16, line 1

Вывод:

        .file   "testing2.cpp"
        .local  _ZStL8__ioinit
        .comm   _ZStL8__ioinit,1,1
        .text
        .globl  main
        .type   main, @function
main:
.LFB969:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        nop
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE969:
        .size   main, .-main
        .type   _Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB970:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        cmpl    $1, -4(%rbp)
        jne     .L3
        cmpl    $65535, -8(%rbp)
        jne     .L3
        movl    $_ZStL8__ioinit, %edi
        call    _ZNSt8ios_base4InitC1Ev
        movl    $__dso_handle, %edx
        movl    $_ZStL8__ioinit, %esi
        movl    $_ZNSt8ios_base4InitD1Ev, %edi
        call    __cxa_atexit
.L3:
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE970:
        .size   _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
        .type   _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB971:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $65535, %esi
        movl    $1, %edi
        call    _Z41__static_initialization_and_destruction_0ii
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE971:
        .size   _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
        .section        .ctors,"aw",@progbits
        .align 8
        .quad   _GLOBAL__sub_I_main
        .hidden __dso_handle
        .ident  "GCC: (GNU) 4.8.2"
        .section        .note.GNU-stack,"",@progbits

С -O3, выход значительно меньше, но все равно никакой разницы.

Ответ 5

Идиома, разработанная на языке C (и унаследованная на С++) для бесконечного цикла, for(;;): отсутствие тестовой формы. Циклы do/while и while не имеют этой специальной функции; их тестовые выражения являются обязательными.

for(;;) не выражает "цикл, пока какое-то условие истинно, что всегда будет истинным". Он выражает "цикл бесконечно". Нет лишнего условия.

Следовательно, конструкцией for(;;) является канонический бесконечный цикл. Это факт.

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

Даже если тестовое выражение while было необязательным, а это не так, while(); было бы странным. while что? Напротив, ответ на вопрос for что? is: why, ever --- навсегда! Как шутка, некоторые программисты прошлых дней определили пустые макросы, чтобы они могли написать for(ev;e;r);.

while(true) превосходит while(1), потому что по крайней мере он не включает в себя kludge, что 1 представляет правду. Однако while(true) не входил в C до C99. for(;;) существует в каждой версии C, возвращаясь к языку, описанному в книге K & R1 1978 года, и на каждом диалекте С++ и даже связанных языках. Если вы кодируете базу кода, написанную на C90, вы должны определить свой собственный true для while (true).

while(true) плохо читается. Хотя верно? Мы действительно не хотим видеть идентификатор true в коде, за исключением случаев, когда мы инициализируем логические переменные или присваиваем им. true не нужно появляться в условных тестах. Хороший стиль кодирования позволяет избежать таких трещин:

if (condition == true) ...

в пользу:

if (condition) ...

По этой причине while (0 == 0) превосходит while (true): он использует фактическое условие, которое проверяет что-то, что превращается в предложение: "цикл, когда нуль равен нулю". Нам нужен предикат, чтобы хорошо сочетаться с "while"; слово "истина" не является предикатом, а реляционным оператором == является.

Ответ 6

Они, вероятно, скомпилируются почти до одного и того же машинного кода, поэтому это вопрос вкуса.

Лично я бы выбрал тот, который является самым ясным (т.е. очень ясно, что он должен быть бесконечным циклом).

Я наклоняюсь к while(true){}.

Ответ 7

Существует ли определенная форма, которую нужно выбрать?

Вы также можете выбрать. Это вопрос выбора. Все они эквивалентны. while(1) {}/while(true){} часто используется для бесконечного цикла программистами.

Ответ 8

Я использую for(;/*ever*/;).

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

Ответ 9

Я бы порекомендовал while (1) { } или while (true) { }. Это то, что написало большинство программистов, и для удобства чтения вы должны следовать общим идиомам.

(Хорошо, поэтому для претензий большинства программистов есть очевидная "цитата". Но из кода, который я видел, на C с 1984 года, я считаю, что это правда.)

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

Ответ 10

Ну, в этом есть много вкуса. Я думаю, что люди из C-фона чаще предпочитают (;;), которые читаются как "навсегда". Если это для работы, сделайте то, что делают локальные жители, если это для вас, сделайте то, что вы можете легко прочитать.

Но, по моему опыту, {} while (1); почти никогда не используется.

Ответ 11

Они одинаковы. Но я предлагаю "while (ture)", который имеет лучшее представление.

Ответ 12

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