C/C++ 中的无限循环 [关闭]

Posted

技术标签:

【中文标题】C/C++ 中的无限循环 [关闭]【英文标题】:Endless loop in C/C++ [closed] 【发布时间】:2013-12-09 19:25:34 【问题描述】:

无限循环有多种可能,我会选择以下几种:

for(;;) while(1) / while(true) do while(1) / do while(true)

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

编辑:如前所述,我忘记了goto,但这样做是因为我根本不喜欢它作为命令。

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

【问题讨论】:

我想你忘了endless: GOTO endless; 我认为您应该选择使代码具有足够可读性的选项,例如 while (true) 你忘记了 GOTO 的可能性。 这里已经有人问过了:***.com/questions/224138/infinite-loops-top-or-bottom @shcherbak。我添加了一个图表。对于也对此感兴趣的人。 【参考方案1】:

问这个问题的问题是你会得到很多主观的答案,只是简单地说“我更喜欢这个......”。我不会做出这种毫无意义的陈述,而是会尝试用事实和参考来回答这个问题,而不是个人意见。

根据经验,我们或许可以从排除 do-while 选项(和 goto)开始,因为它们并不常用。我不记得曾经在由专业人士编写的实时生产代码中看到它们。

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


for(;;)

这是永恒循环的原始规范示例。在 Kernighan 和 Ritchie 所著的古代 C 圣经The C Programming Language中,我们可以读到:

K&R 第 2 版 3.5:

for (;;) 
...

是一个“无限”循环,可能会被其他方式打破,例如 作为休息或回归。使用 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

/--/

clause-1 和 expression-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,这种形式还有一个缺点,即它使用了 stdbool.h 中的宏 true。所以为了使这个编译,我们需要包含一个头文件,这可能会或可能不会不方便。在 C++ 中这不是问题,因为 bool 作为原始数据类型存在,而 true 是语言关键字。

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


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

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

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

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

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

【讨论】:

你的粗体字就是事实。 @Lundin:我觉得这句话有误导性>>"我不会做出这样毫无意义的陈述,我会尝试用事实和参考来回答这个问题,而不是个人意见。”.......因为最后你写的所有观点都是XYZ的“个人意见”。如果您发现其他答案是“毫无意义的陈述”,那么我在您的答案中找不到任何东西,这与其他答案不同! @Nawaz ISO 标准委员会第 14 工作组的“个人意见”? :) 这句话主要针对那些会写“使用 for(;;) 因为我是 random_dude_999 堆栈溢出,我这么说”的人。我的答案的最后一部分包含个人意见,但我明确标记为这样。其他大部分内容要么可以从标准中引用,要么是从经验中学到的“事实”陈述。例如,任何资深的 C 程序员都可以保证 do-while(1) 没有在实际代码中使用,但很难找到可以引用的来源。 @Lundin 我的观点与这个问题有关,对你的回答更重要的是,健全编程实践的概念比你想象的要古老得多,它的实现也更加主观思考。不用想像力就能看出,如果 30 年前被认为是声音的东西现在被认为是坏的,那么再过 30 年,现在被认为是声音的东西...... 作为K+R的书出版时已经是程序员的人,我完全同意@user1681572。除了 OO 问题,今天被认为是“良好的现代编程实践”的所有主要元素都已经存在并得到广泛支持。 K+R 只是不同意他们的观点,其副作用是最终创造了一种完全占主导地位的编程亚文化,这种亚文化需要数十年才能恢复到 70 年代已知的水平。【参考方案2】:

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

while(true)  //in C++

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

有人可能会说for(;;) 也很清楚。但我会争辩说,由于其复杂的语法,这个选项需要额外的知识才能得出它是一个无限循环的结论,因此它相对较少 清楚。我什至会说有 更多 数量的程序员不知道 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 程序员都写这个,我找不到任何偏离规范的理由。

【讨论】:

for(;;) 非常清楚,我还没有遇到另一个(有经验的)程序员不知道它类似于无限循环。另外,它的字符更少。 @miguel.martin 但它包括一张哭泣的脸。 @Maroun 当眼泪停止时循环终止。 @miguel.martin: 你应该再看一遍你的评论,知道为什么for(;;) 不太清楚>> "for(;;) 很清楚,我还没有认识另一个不知道它类似于无限循环的(经验丰富的)程序员。”(强调我的)。为什么您觉得有必要在评论中写"(experienced)"?你只为有经验的程序员写代码? @Nawaz 主要出于两个原因,第一个是击键次数较少,第二个在逻辑上确实意味着真正的正常。所以可读性。【参考方案3】:

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

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

这是一个 for 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

还有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

【讨论】:

+1 用于实际调查汇编代码 在linux上,我做了gcc -S -o test1.asm testing.cpp,三个不同的循环放在单独的文件里,做了cmp test1.asm test2.asm等等,好像没什么区别。 +1 无聊创造了有用的东西:) 不幸的是,我对汇编不够熟悉,无法知道所有这些意味着什么。和@remyabel我的gcc版本是Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1 Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn) Target: x86_64-apple-darwin13.0.0 Thread model: posix出于好奇,你介意分享你的gcc版本 @user1216838 查看我的回答。【参考方案4】:

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

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

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

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

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

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

根据SLaks,它们的编译方式相同。

Ben Zotto also says it doesn't matter:

这并不快。 如果您真的很在意,请为您的平台使用汇编器输出进行编译并查看。 没关系。这一点都不重要。随心所欲地编写无限循环。

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

【讨论】:

我是一个经验丰富的开发者,鄙视while (true) +1 用于实际回答问题并显示编译器如何不在乎您使用什么。【参考方案5】:

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

for(;;) 没有表达“循环,而某些条件为真,碰巧总是为真”。它表示“无限循环”。没有多余的条件。

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

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

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

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

while(true) 读取错误。 什么是真的?我们真的不想在代码中看到标识符true,除非我们正在初始化布尔变量或分配给它们。 true 不需要出现在条件测试中。良好的编码风格可以避免这样的繁琐:

if (condition == true) ...

赞成:

if (condition) ...

因此while (0 == 0)优于while (true):它使用了一个实际条件来测试某些东西,这变成了一个句子:“loop while zero is equal to zero”。我们需要一个谓词来很好地与“while”配合; “true”这个词不是谓词,但关系运算符== 是。

【讨论】:

我不知道;我想我自己更喜欢for(ever;and;ever);。也许这是个人喜好的问题。 :) #define forever for(;;)【参考方案6】:

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

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

【讨论】:

或者只是#define forever while(1)。永远 ... @Dmitry:你可以这样做,但这会破坏即时可读性。其他阅读代码的人必须查找定义以确保 forever 确实是一个永恒循环。添加 /*ever*/ 只会增加一点可读性。 是的,起初它令人困惑,但它有两个好处。首先,在智能编辑器中将鼠标悬停在它上面会显示#define forever while(1),即使是初学者也应该很清楚。其次,它总体上比其他循环的意图更清楚。不久前,foreach 循环还很异端,乍一看并不明显,现在它们已被接受为标准。虽然我个人更喜欢“重复”而不是“永远”,因为循环实际上并没有永远循环,因为它可能会被中断。公平地说,初学者会看到 while(1) 和重复 一样令人困惑,甚至更多。 虽然我喜欢“#define repeat for (;/*ever*/;)”,但标准语法高亮的丢失可能不值得宏,所以我更喜欢原始版本。可惜语言本身不会采用“repeat/forever ”【参考方案7】:

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

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

我倾向于while(true)

【讨论】:

【参考方案8】:

我会推荐while (1) while (true) 。这是大多数程序员会写的,出于可读性的原因,您应该遵循常见的习惯用法。

(好吧,对于大多数程序员的说法,显然“需要引用”。但从我看到的代码来看,自 1984 年以来的 C 代码,我相信这是真的。)

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

【讨论】:

【参考方案9】:

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

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

【讨论】:

@Dowvoters;小心解释。【参考方案10】:

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

但根据我的经验,做 while (1);几乎从不使用。

【讨论】:

【参考方案11】:

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

【讨论】:

-1:这个答案对这个问题没有任何意义。【参考方案12】:

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

【讨论】:

-1:这个答案对这个问题没有任何意义。 ture??那不会编译!

以上是关于C/C++ 中的无限循环 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

Matlab GUI中的无限循环导致关闭GUI时Matlab冻结?

打破节点repl中的无限循环?

双向链表无限循环? [关闭]

C ++中的无限[关闭]

Saxonica EE Xslt 转换处理无限循环

如果其他条件产生无限循环,则继续语句[关闭]