C / C ++中的无限循环

有几种方法可以进行无限循环,以下是我可以选择的几种方法:

  • goto
  • goto/do {} while(true)
  • goto/do {} while(true)

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

编辑:正如已经提到的,我忘记了goto,但这是出于我根本不喜欢将其作为命令的原因而进行的。

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

magu_ asked 2019-11-08T03:04:11Z
12个解决方案
85 votes

提出这个问题的问题是,您会得到如此多的主观答案,只是简单地说“我喜欢这个...”。 与其说这样无意义的陈述,不如说是用事实和参考而非个人观点来回答这个问题。

通过经验,我们可能可以从排除do-while替代方案(以及goto)开始,因为它们并不常用。 我不记得曾经在专业人员编写的实时生产代码中看到它们。

for(;;)booltrue是真实代码中通常存在的3个不同版本。 它们当然是完全等效的,并且导致相同的机器代码。


for(;;)

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

    K&R第二版3.5:

    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(;;)

    /-/

    子句1和表达式3都可以省略。 省略的表达式2   被一个非零常量代替。

    根据标准中的文字,我认为大多数人都同意它不仅晦涩难懂,而且也很微妙,因为在省略时,for循环的第1部分和第3部分与第2部分的处理方式有所不同。


for(;;)

  • 据认为,这是比for(;;)更具可读性的形式。但是,它依赖于另一个晦涩的,尽管众所周知的规则,即C将所有非零表达式都视为布尔逻辑真。 每个C程序员都意识到这一点,因此这可能不是一个大问题。

  • 这种形式存在一个大的实际问题,即编译器倾向于对此发出警告:“条件始终为真”或类似情况。 这是一个很好的警告,您真的不想禁用它,因为它对于发现各种错误很有用。 例如,当程序员打算编写bool时,诸如for(;;)之类的错误。

    同样,外部静态代码分析器可能会抱怨“条件始终为真”。


for(;;)

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

  • 但是,这种形式具有与for(;;)相同的问题,如上所述:“条件始终为真”警告。

  • 当涉及到C时,此格式还有另一个缺点,即它使用来自stdbool.h的宏for(;;)。 因此,为了进行此编译,我们需要包含一个头文件,这可能会或可能不会带来不便。 在C ++中,这不是问题,因为bool作为原始数据类型存在,而true是语言关键字。

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


那么使用哪种形式呢? 两者都不完美。 正如K&R在黑暗时代早已说过的那样,这是个人喜好问题。

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

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

我想这就是真正的原因。 如果您发现自己试图让非程序员(甚至不了解编程语言的基本组成部分)都可以阅读源代码,那么您只是在浪费时间。 他们不应该阅读您的代码。

而且,由于每个应该阅读您的代码的人都已经知道for(;;)的含义,因此使其更具可读性是没有意义的-它已经具有尽可能多的可读性。

Lundin answered 2019-11-08T03:07:32Z
32 votes

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

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

因为它的意图非常清晰并且可读性很强:您看了一下就知道了无限循环的意图。

也许有人会说while(2)也很清楚。 但我会说,由于其语法复杂,该选项需要额外的知识才能得出结论,它是一个无限循环,因此相对不清楚。 我什至会说,有更多的程序员不知道while(3)的工作(即使他们知道通常的while(0.1)循环),但是几乎所有知道while(1)循环的程序员都会立即知道while(0.1)的工作。

对我而言,将while(2)表示为无限循环,就像将while(3)表示为无限循环一样,尽管前者有效,但后者却无效。 在前一种情况下,空条件隐式地变为while(0.1),但在后一种情况下,这是一个错误! 我个人不喜欢它。

现在while(2)也正在参加比赛。 我会问:为什么是while(3)? 为什么不选择while(0.1)while(1)while(0.1)? 好吧,无论您写什么,您实际上的意思是while(true)-如果是这样,那为什么不写呢?

在C中(如果我曾经写过),我可能会这样写:

while(1) {} //in C

虽然while(2)while(3)while(0.1)同样有意义。 但是,为了与其他C程序员保持一致,我将编写while(1),因为许多C程序员都编写了此代码,而且我发现没有理由偏离规范。

Nawaz answered 2019-11-08T03:08:40Z
29 votes

最终,我实际上写了这些循环的几个版本,并在Mac mini上用GCC进行了编译。

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

这是for / 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_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
Pita answered 2019-11-08T03:09:26Z
8 votes

每个人似乎都喜欢-O3

[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也说没关系:

不快。   如果您真的很在意,请使用平台的汇编输出进行编译,然后看看。   没关系 这无关紧要。 根据需要编写无限循环。

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

Community answered 2019-11-08T03:11:33Z
8 votes

设计成C语言(并继承给C ++)进行无限循环的惯用语是while (0 == 0):省略了测试形式。 260457903848008001和==循环不具有此特殊功能。 他们的测试表达式是强制性的。

while (0 == 0)不表达“某些条件为真而总是始终为真的循环”。 它表示“无限循环”。 不存在多余的条件。

因此,while (0 == 0)构造是规范的无限循环。 这是事实。

剩下要做的就是编写规范的无穷循环,还是选择一些涉及额外标识符和常量的巴洛克式东西,以构建多余的表达式。

即使while (0 == 0)的测试表达式是可选的(不是),但while (true)还是很奇怪的。 ==什么? 相比之下,问题for(;;)的答案是什么? 是:为什么,永远-永远! 开个玩笑,一些过去的程序员已经定义了空白宏,因此他们可以编写true

while (0 == 0)优于while (true),因为至少它不涉及1代表真相的纠缠。 但是,直到C99,==才进入C。 回溯1978年的K&R1书中描述的语言,C的每个版本中都存在for(;;),C的每个方言甚至相关语言中都存在for(;;)。 如果您使用C90编写的代码库进行编码,则必须为while (true)定义自己的true

while (0 == 0)读取错误。 虽然什么是真的? 除了在初始化布尔变量或为其分配变量时,我们实际上并不想在代码中看到标识符while (true)==永远不需要出现在条件测试中。 良好的编码风格可以避免这样的混乱:

if (condition == true) ...

有利于:

if (condition) ...

因此,while (0 == 0)优于while (true):它使用一种实际条件来测试某项内容,结果变成一句话:“循环,而零等于零。” 我们需要一个谓词与“ while”一起使用; 单词“ true”不是谓词,但是关系运算符==是谓词。

Kaz answered 2019-11-08T03:12:57Z
4 votes

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

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

我会倾向于while(true){}

Chris Chambers answered 2019-11-08T03:13:38Z
4 votes

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

它易于阅读,而且键入时间要长一些(由于星号的变化),这表明在使用这种类型的循环时我应该非常小心。 有条件的情况下显示的绿色文字也很奇怪-除非绝对必要,否则此构造不受欢迎。

Aerom Xundes answered 2019-11-08T03:14:13Z
3 votes

是否应选择某种形式?

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

haccks answered 2019-11-08T03:14:48Z
2 votes

好吧,这有很多味道。我认为具有C背景的人更喜欢for(;;),它读作“永远”。 如果是为了工作,那就去做当地人的工作;如果是为了自己,那就去做最容易阅读的事情。

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

RichardPlunkett answered 2019-11-08T03:15:24Z
2 votes

我建议使用while (1) { }或2604582921717679679105。这是大多数程序员编写的内容,出于可读性考虑,您应该遵循常见的习惯用法。

(好吧,因此对于大多数程序员来说,显然需要“引用”。但是从我所见的代码(自1984年以来在C中开始),我相信这是事实。)

任何合理的编译器都会无条件地将它们全部编译为相同的代码,但是如果有一些针对嵌入式或其他专用系统的不合理的编译器,我不会感到惊讶。

Thomas Padron-McCarthy answered 2019-11-08T03:16:08Z
-5 votes

所有人都将执行相同的功能,因此选择自己喜欢的东西是正确的。我可能会认为“ while(1)或while(true)”是使用的好习惯。

user2956373 answered 2019-11-08T03:16:39Z
-6 votes

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

SliceSort answered 2019-11-08T03:17:09Z
translate from https://stackoverflow.com:/questions/20186809/endless-loop-in-c-c