C/C++中的无限循环

mag*_*gu_ 57 c c++ loops infinite-loop

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

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

是否有某种形式应该选择?现代编译器是否会在中间和最后一个语句之间产生影响,或者它是否意识到它是一个无限循环并完全跳过检查部分?

编辑:因为已经提到我忘记了goto,但这是因为我根本不喜欢它作为一个命令.

Edit2:我对从kernel.org获取的最新版本做了一些grep.我确实看起来随着时间的推移没有太大变化(至少在内核中) 在此输入图像描述

Lun*_*din 85

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

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

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


for(;;)

  • 这是永恒循环的原始,规范的例子.在Kernighan和Ritchie 的古老的C语言编程语言中,我们可以读到:

    K&R第二版3.5:

    for (;;) {
    ...
    }
    
    Run Code Online (Sandbox Code Playgroud)

    是一个"无限"的循环,可能被其他方式打破,例如休息或回归.是否使用while或for主要取决于个人喜好.

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

    但是,K&R自己已经表示这是一个偏好问题.

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

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

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

    ISO 9899:2011 6.8.5.3:

    for ( clause-1 ; expression-2 ; expression-3 ) statement
    
    Run Code Online (Sandbox Code Playgroud)

    / - /

    可以省略子句-1和表达式3.省略的表达式-2由非零常量替换.

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


while(1)

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

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

    此外,外部静态代码分析器可能会抱怨"条件总是如此".


while(true)

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

  • 但是,while(1)如上所述,这种形式具有相同的问题:"条件始终为真"警告.

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

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


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

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

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

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

因为每个应该 阅读你的代码的人都已经知道了什么for(;;)意思,所以没有必要让它更具可读性 - 它已经具备了可读性.

  • @Lundin:我发现这一行误导了>>*"**我会尝试用事实和参考文献回答这个问题,而不是个人意见**."*... .......因为最后你写的所有要点都是XYZ的"个人意见".如果你发现其他答案是"毫无意义的陈述",那么我在你的答案中找不到任何答案,这与其他答案不同! (10认同)
  • 作为K + R出版时已经是程序员的人,我完全同意@ user1681572.除了面向对象的事项,今天被认为是"*优秀的现代编程实践*"的所有主要元素已经存在并得到广泛支持.K + R只是不同意它们,并且作为一种副作用最终创造了一个完整的主导编程亚文化,需要几十年才能回到70年代已经知道的东西. (3认同)
  • 你的粗体文字就是真相。 (2认同)
  • @Lundin我的观点,这是相关的问题,甚至更使你的答案是,声音的编程实践的概念是老得多,其实现更主观的,比你似乎认为.如果30年前被视为声音的东西现在被认为是坏的,那么在未来的30年里现在被认为是合理的......这并没有想象力的飞跃. (2认同)

Naw*_*waz 32

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

while(true) {} //in C++
Run Code Online (Sandbox Code Playgroud)

因为它的意图非常明确并且它也是可读的:你看它并且你知道 无限循环.

有人可能会说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
Run Code Online (Sandbox Code Playgroud)

虽然while(2),while(3)并且while(0.1)同样有道理.但是为了与其他C程序员保持一致,我会写while(1),因为很多C程序员都写这个,我发现没有理由偏离常规.

  • @ miguel.martin但它包括一张哭泣的脸. (9认同)
  • @Maroun当眼泪停止时,循环终止. (8认同)
  • for(;;)非常清楚,我还没有遇到另一个(有经验的)程序员,它不知道它类似于无限循环.此外,它写的字符更少. (4认同)
  • @ miguel.martin:你应该重新阅读你的评论,要知道为什么`for(;;)`相对不太清楚>>*"for(;;)非常清楚,我还没有见到另一个**(经验丰富的**程序员,不知道它类似于无限循环."*(强调我的).为什么你觉得*需要在你的评论中写*"(经验丰富)"*?您只为经验丰富的*程序员编写代码? (3认同)

Pit*_*ita 29

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

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
Run Code Online (Sandbox Code Playgroud)

和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
Run Code Online (Sandbox Code Playgroud)

  • +1实际调查汇编代码的努力 (2认同)

小智 8

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

/sf/answers/15689971/

/sf/answers/98081861/

/sf/answers/98081581/

/sf/answers/98081511/

/sf/answers/98082351/

根据SLaks的说法,他们编译相同.

Ben Zotto也说没关系:

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

作为对用户1216838的回应,这是我尝试重现他的结果.

这是我的机器:

cat /etc/*-release
CentOS release 6.4 (Final)
Run Code Online (Sandbox Code Playgroud)

gcc版本:

Target: x86_64-unknown-linux-gnu
Thread model: posix
gcc version 4.8.2 (GCC)
Run Code Online (Sandbox Code Playgroud)

和测试文件:

// 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; }
}
Run Code Online (Sandbox Code Playgroud)

命令:

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
Run Code Online (Sandbox Code Playgroud)

唯一的区别是第一行,即文件名.

test1.asm test2.asm differ: byte 16, line 1
Run Code Online (Sandbox Code Playgroud)

输出:

        .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
Run Code Online (Sandbox Code Playgroud)

随着-O3,输出当然是相当小,但仍然没有什么区别.

  • 我是一个经验丰富的开发人员,我鄙视`while(true)`. (3认同)

Kaz*_*Kaz 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&R1中所描述的语言,以及C++的每种方言,甚至是相关语言.如果你在写的C90代码库的编码,你必须定义自己truewhile (true).

while(true)读得很糟糕.而什么是真实的?我们真的不想true在代码中看到标识符,除非我们初始化布尔变量或分配它们.true不必出现在条件测试中.良好的编码风格避免像这样:

if (condition == true) ...
Run Code Online (Sandbox Code Playgroud)

有利于:

if (condition) ...
Run Code Online (Sandbox Code Playgroud)

由于这个原因while (0 == 0)优于while (true):它使用测试某些东西的实际条件,变成一个句子:"循环,而零等于零." 我们需要一个谓词与"while"很好地结合; 单词"true"不是谓词,而是关系运算符==.

  • 我不知道; 我觉得我更喜欢'永远;永远;'我自己.也许这是个人偏好的问题.`:)` (3认同)

Aer*_*des 6

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

它很容易阅读,而且打字需要更长的时间(由于星号的变化),这表明我在使用这种类型的循环时应该非常小心。出现在条件句中的绿色文本也是一个非常奇怪的景象——除非绝对必要,否则这个结构是不受欢迎的另一个迹象。

  • @Dmitry:你_可以_这样做,但这会破坏即时可读性。阅读代码的其他人必须查找定义以确保“永远”确实是一个永恒的循环。添加 /*ever*/ 只会增加一点额外的可读性。 (2认同)

Chr*_*ers 5

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

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

我会倾向于while(true){}