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

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

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

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

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

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

12 ответов

Решение

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

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

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


for(;;)

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

    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. Таким образом, чтобы сделать эту компиляцию, нам нужно включить файл заголовка, который может быть или не быть неудобным. В C++ это не проблема, так как bool существует как примитивный тип данных и true это ключевое слово языка.

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


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

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

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

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

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

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

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) в равной степени имеет смысл. Но просто чтобы быть совместимым с другими программистами C, я бы написал while(1)потому что многие программисты на Си пишут это, и я не вижу причин отклоняться от нормы.

В полной скуке я написал несколько версий этих циклов и скомпилировал их с GCC на моем Mac Mini.

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

вот тот, для цикла while/for

    .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

и цикл делать пока

        .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

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

/questions/9929964/beskonechnyie-petli-verh-ili-niz/9929982#9929982

/questions/26059208/kakoj-pravilnyij-c-beskonechnyij-tsikl-dlya-ili-while-true/26059215#26059215

/questions/26059208/kakoj-pravilnyij-c-beskonechnyij-tsikl-dlya-ili-while-true/26059221#26059221

/questions/26059208/kakoj-pravilnyij-c-beskonechnyij-tsikl-dlya-ili-while-true/26059225#26059225

/questions/26059208/kakoj-pravilnyij-c-beskonechnyij-tsikl-dlya-ili-while-true/26059213#26059213

Согласно 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, конечно, значительно меньше, но разницы нет.

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

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

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

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

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

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

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

if (condition == true) ...

в пользу:

if (condition) ...

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

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

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

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

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

Я бы склонялся while(true){},

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам