C / C ++中的无限循环

时间:2013-11-25 07:23:38

标签: c++ c loops infinite-loop

有几种可能性来进行无限循环,这里有一些我会选择:

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

是否有某种形式应该选择?现代编译器在中间和最后一个语句之间做了区别,还是意识到它是一个无限循环并完全跳过检查部分?

编辑:正如我刚才提到的那样,我忘了goto,但这是因为我根本不喜欢它作为一个命令。

Edit2:我对从kernel.org获取的最新版本做了一些grep。我确实看起来随着时间的推移没有太大变化(至少在内核中) enter image description here

12 个答案:

答案 0 :(得分:82)

提出这个问题的问题在于,你会得到如此多的主观答案,而这些答案只是简单地陈述了#34;我更喜欢这个......"。我没有做出这些毫无意义的陈述,而是试图用事实和参考来回答这个问题,而不是个人意见。

通过经验,我们可以从排除do-while替代品(和goto)开始,因为它们不常用。我不记得曾经在专业人士撰写的现场制作代码中看过它们。

while(1)while(true)for(;;)是实际代码中常见的3种不同版本。它们当然完全等效,并产生相同的机器代码。


<强> for(;;)

  • 这是永恒循环的原始,规范示例。在Kernighan和Ritchie的古老的C语言 The C Programming Language 中,我们可以读到:

    K&amp; R 2nd ed 3.5:

    for (;;) {
    ...
    }
    
      

    是一个无限的&#34;循环,可能是通过其他方式打破,如此   作为休息或回归。是否使用while或for在很大程度上是一个问题   个人偏好。

    很长一段时间(但不是永远),这本书被认为是经典和C语言的定义。由于K&amp; R决定展示for(;;)的一个例子,至少在1990年的C标准化之前,这将被认为是最正确的形式。

    然而,K&amp; R本身已经表示这是一个偏好的问题。

    今天,K&amp; R是一个非常可疑的来源,可用作规范的C参考。它不仅过时了几次(不是解决C99或C11),它还宣扬了在现代C语言编程中通常被认为是坏的或明显危险的编程实践。

    尽管K&amp; R是一个值得怀疑的来源,但这个历史方面似乎是支持for(;;)的最强有力的论据。

  • 针对for(;;)循环的论点是它有些模糊且不可读。要了解代码的作用,您必须知道标准中的以下规则:

    ISO 9899:2011 6.8.5.3:

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

    / - /

      

    可以省略子句-1和表达式-3。省略的表达式-2   被非零常数替换。

    基于标准中的这个文本,我认为大多数人会同意它不仅模糊不清,而且也很微妙,因为for循环的第1和第3部分与第2部分的处理方式不同,省略时。 / p>


<强> while(1)

  • 这被认为是比for(;;)更易读的形式。然而,它依赖于另一个模糊的,虽然众所周知的规则,即C将所有非零表达式视为布尔逻辑真。每个C程序员都知道这一点,因此不太可能是一个大问题。

  • 这种形式存在一个很大的实际问题,即编译器倾向于给出警告:&#34;条件总是正确的&#34;或类似的。这是一个很好的警告,你真的不想禁用它,因为它对于发现各种错误很有用。例如,while(i = 1)这样的错误,当程序员打算写while(i == 1)时。

    此外,外部静态代码分析器可能会抱怨&#34;条件始终为真&#34;。


<强> while(true)

  • 为了使while(1)更具可读性,有些人会使用while(true)。程序员之间的共识似乎是这是最易读的形式。

  • 但是,此表单与while(1)具有相同的问题,如上所述:&#34;条件始终为真&#34;警告。

  • 说到C,这个表单有另一个缺点,即它使用stdbool.h中的宏true。因此,为了进行此编译,我们需要包含一个头文件,这可能会或可能不会很不方便。在C ++中,这不是问题,因为bool作为基本数据类型存在,true是语言关键字。

  • 这种形式的另一个缺点是它使用的是C99 bool类型,它只能在现代编译器上使用,而不能向后兼容。同样,这只是C中的问题,而不是C ++中的问题。


那么使用哪种形式?似乎都不完美。正如K&amp; R已经在黑暗时代所说的那样,这是个人偏好的问题。

就个人而言,我总是使用for(;;)来避免其他表单经常生成的编译器/分析器警告。但也许更重要的是因为:

如果即使是C初学者知道 for(;;)意味着一个永恒的循环,那么你是谁试图让代码更具可读性?

我想这就是它真正归结为什么。如果你发现自己试图让非程序员可以理解你的源代码,那些人甚至不知道编程语言的基本部分,那么你只是在浪费时间。他们不应该阅读你的代码。

由于每个 都应该阅读你的代码的人已经知道for(;;)意味着什么,所以没有必要让它更具可读性 - 它已经具有可读性。

答案 1 :(得分:32)

这是非常主观的。我写这个:

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

因为它的意图非常清晰并且它也是可读的:你看看它并且知道无限循环。

有人可能会说for(;;)也很清楚。但我认为,由于其错综复杂的语法,这个选项需要额外的知识来得出它是无限循环的结论,因此它相对较少< / em>清楚。我甚至会说有更多的程序员不知道for(;;)做了什么(即使他们知道通常的for循环),但几乎所有知道while循环的程序员都会立即弄明白while(true)的作用。

对我而言,将for(;;)写成无限循环,就像写while()来表示无限循环 - 而前者有效,后者则无效。在前一种情况下, empty 条件隐含地为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),因为很多C程序员都写这个,我发现没有理由偏离常规。

答案 2 :(得分:29)

在无聊的终极行为中,我实际上写了这些循环的几个版本,并在我的mac mini上用GCC编译它。

的     while(1){}for(;;) {} 产生相同的装配结果 虽然do{} while(1);产生了类似但不同的汇编代码

继承人/ for 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

和do 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

答案 3 :(得分:8)

每个人似乎都喜欢while (true)

https://stackoverflow.com/a/224142/1508519

https://stackoverflow.com/a/1401169/1508519

https://stackoverflow.com/a/1401165/1508519

https://stackoverflow.com/a/1401164/1508519

https://stackoverflow.com/a/1401176/1508519

根据SLaks,他们编译相同。

Ben Zotto also says it doesn't matter

  

它并不快。   如果您真的关心,请使用您的平台的汇编程序输出进行编译,并查看。   没关系。这永远不重要。写下你喜欢的无限循环。

在回复用户1216838时,我试图重现他的结果。

这是我的机器:

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,输出当然要小得多,但仍然没有区别。

答案 4 :(得分:8)

用于无限循环的C语言(并继承到C ++中)的习语是for(;;):省略了测试形式。 do/whilewhile循环没有此特殊功能;他们的测试表达是强制性的。

for(;;)不表达“循环,而某些条件为真,恰好是真的”。它表达了“无休止地循环”。没有多余的条件。

因此,for(;;)构造是规范无限循环。这是事实。

所有留待观点的是,是否要编写规范的无限循环,或选择一些涉及额外标识符和常量的巴洛克式,以构建多余的表达式。

即使while的测试表达式是可选的,它不是,while();也会很奇怪。 while什么?相比之下,问题for的答案是什么?是:为什么,永远 - 永远!作为一个笑话,一些过去的程序员已经定义了空白宏,因此他们可以写for(ev;e;r);

while(true)优于while(1),因为至少它不涉及1代表真理的kludge。但是,while(true)直到C99才进入C. for(;;)存在于C的每个版本中,这些版本可以追溯到1978年出版的K&amp; R1中所描述的语言,以及C ++的每种方言,甚至是相关语言。如果您使用C90编写的代码库进行编码,则必须为true定义自己的while (true)

while(true)读得很糟糕。虽然什么是真的?我们真的不希望在代码中看到标识符true,除非我们初始化布尔变量或分配它们。 true不需要出现在条件测试中。良好的编码风格避免了这样的残酷:

if (condition == true) ...

支持:

if (condition) ...

出于这个原因while (0 == 0)优于while (true):它使用一个测试某事的实际条件,变成一个句子:“循环,而零等于零。”我们需要一个谓词与“while”完美结合;单词“true”不是谓词,但关系运算符==是。

答案 5 :(得分:4)

他们可能编译成几乎相同的机器代码,所以这是一个品味问题。

就个人而言,我会选择最清晰的那个(即很清楚它应该是一个无限循环)。

我倾向于while(true){}

答案 6 :(得分:4)

我使用for(;/*ever*/;)

它易于阅读并且输入需要更长的时间(由于星号的移位),表明在使用这种类型的循环时我应该非常小心。在条件中显示的绿色文本也是一个非常奇怪的景象 - 除非绝对必要,否则这个构造不赞成的另一个迹象。

答案 7 :(得分:3)

  

是否有某种形式应该选择?

您可以选择其中之一。它的选择问题。一切都是等价的。 while(1) {}/while(true){}经常被程序员用于无限循环。

答案 8 :(得分:2)

我建议while (1) { }while (true) { }。这是大多数程序员所写的,出于可读性的原因,你应该遵循常用的习语。

(好吧,对大多数程序员来说,有一个明显的“引用需要”。但从我见过的代码中,自1984年以来,我认为这是真的。)

任何合理的编译器都会使用无条件跳转将所有这些编译器编译为相同的代码,但对于嵌入式或其他专用系统,如果有一些不合理的编译器,我不会感到惊讶

答案 9 :(得分:2)

嗯,这个有很多味道。 我认为来自C背景的人更可能更喜欢(;;),其读作“永远”。如果是为了工作,那就做当地人做的事情,如果是你自己做的话,做一个你最容易阅读的那个。

但根据我的经验,做{}而(1);几乎从未使用过。

答案 10 :(得分:-5)

所有人都将执行相同的功能,选择你喜欢的是真的。 我可能认为“while(1)或while(true)”是使用的好习惯。

答案 11 :(得分:-6)

他们是一样的。但我建议“while(ture)”具有最好的代表性。