C / C ++中无尽循环

有几种可能做无限循环,这里有一些我会select:

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

有一种应该select的forms吗? 现代编译器会在中间语句和最后一个语句之间做出区别,还是会意识到这是一个无限循环,完全跳过了检查部分?

编辑:正如前面提到的,我忘记了goto ,但这是因为我根本不喜欢它作为命令。

Edit2:我对从kernel.org取得的最新版本做了一些grep。 我似乎没有太多的改变(至less在内核内) 在这里输入图像说明

问这个问题的问题是,你会得到这么多的主观答案,只是陈述“我更喜欢这个…”。 我不会做出这样毫无意义的陈述,而是试着用事实和参考来回答这个问题,而不是个人意见。

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

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


for(;;)

  • 这是一个永恒循环的原始的,典型的例子。 在Kernighan和Ritchie 的C语言程序devise语言中 ,我们可以读到:

    K&R 2nd ed 3.5:

     for (;;) { ... } 

    是一个“无限”的循环,可能被其他方式所打破,比如rest或回归。 不pipe是用还是用,主要是个人喜好的问题。

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

    然而,K&R本身已经表示,这是一个优先事项。

    而今天,K&R是一个非常可疑的来源,可以用作规范的C参考。 它不仅过时了几次(不是针对C99和C11),而且还鼓吹在现代C编程中通常被认为是坏的或公然危险的编程实践。

    但是尽pipeK&R是一个可疑的来源,但这个历史方面似乎是支持for(;;)的最有力的论据。

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

    ISO 9899:2011 6.8.5.3:

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

    / – /

    子句1和expression式3都可以省略。 省略的expression式-2被非零常量替代。

    基于这个标准的文本,我认为大多数人会认为,它不仅是模糊的,而且也是微妙的,因为for循环的第一和第三部分的处理与第二部分不同,当省略时。


while(1)

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

  • 这种forms存在着一个很大的实际问题,即编制者倾向于给予警告:“条件永远是真的”或类似的。 这是一个很好的警告,一种你真正不想禁用的警告,因为它对于发现各种错误很有用。 比如一个bug,比如while(i = 1) ,当程序员打算写的while(i == 1)

    此外,外部静态代码分析器可能会抱怨“条件永远是真的”。


while(true)

  • 为了使while(1)更具可读性,有的使用while(true)来代替。 程序员之间的共识似乎是这是最可读的forms。

  • 但是,这种forms与while(1)中所述的问题相同,如上所述:“条件永远是真实的”警告。

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

  • 这种forms的另一个缺点是它使用C99布尔types,它只能在现代编译器上使用,而不能向后兼容。 再一次,这只是C中的问题,而不是C ++中的问题。


那么使用哪种forms? 两者似乎都不完美。 正如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) ,因为很多C程序员都这么写,我没有理由偏离规范。

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

while(1){}for(;;) {}产生相同的程序集结果,而do{} while(1); 产生了类似但不同的汇编代码

inheritance人为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 

和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 

每个人似乎都喜欢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 ,他们编译相同。

本Zotto也说这并不重要 :

这不是更快。 如果你真的在意,编译汇编输出为您的平台,并期待看到。 没关系。 这永远不重要。 写你的无限循环,但你喜欢。

为了响应用户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文件:

 // 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语言(并inheritance到C ++)的成语是for(;;) :省略了testing表单。 do/whilewhile循环没有这个特殊function; 他们的testingexpression式是必须的

for(;;)不表示“循环,而某些条件是真的,总是是真的”。 它expression“循环无休止”。 没有多余的条件存在。

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

所有留下来的意见是要写出规范的无限循环,还是要select一些涉及额外的标识符和常量的巴洛克式的东西来构build一个多余的expression式。

即使while的testingexpression式是可选的,它不是, while(); 会很奇怪 while什么? 相比之下,这个问题的答案for什么? 是:为什么,永远—永远! 作为一个笑话,一些过去的程序员定义了空白的macros,所以他们可以写for(ev;e;r);

while(true)优于while(1)因为至less它不涉及1代表真理的混合物。 然而, while(true)在C99之前没有进入C. for(;;)存在于C语言的每一个版本中,可以追溯到1978年出版的K&R1和C ++的各种方言,甚至相关的语言。 如果您使用C90编写代码库,则必须在while (true)定义自己的true

while(true)读得不好。 而什么是真实的? 除非我们正在初始化布尔variables或赋值给它们,否则我们不希望在代码中看到标识符为truetrue不需要出现在条件testing中。 良好的编码风格避免了这样的结果:

 if (condition == true) ... 

有利于:

 if (condition) ... 

因为这个原因, while (0 == 0)优于while (true) :它使用一个实际的条件来testing某个东西,这个条件变成了一个句子:“零等于零的循环”。 我们需要一个谓词来和“while”搭配得很好。 “真”这个词不是谓词,而是关系运算符==

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

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

我会倾向于while(true){}

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

这很容易阅读,而且input时间稍长(由于星号的变化),表明在使用这种types的循环时应该非常小心。 在有条件的情况下出现的绿色文本也是一个非常奇怪的景象 – 另一个迹象表明,除非绝对必要,否则这种构造是不被接受的。

有一种应该select的forms吗?

你可以select。 它的select的问题。 全部是相同的。 while(1) {}/while(true){}经常被程序员用于无限循环。

好吧,这里有很多味道。 我认为来自C背景的人更倾向于(;;),这个名字是“永远”的。 如果它的工作,做本地人做的,如果它自己,做一个你最容易阅读。

但以我的经验,{} while(1); 几乎从来没有使用过。

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

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

任何合理的编译器都会将它们全部编译成相同的代码,并且无条件地跳转,但是对于embedded式系统或其他专用系统,如果存在一些不合理的编译器,我不会感到惊讶。

他们是一样的。 但是我build议有“最好的代表性”。

所有的人都会执行相同的function,select你喜欢的东西是对的。我可能认为“while(1)while while(true)”是一个很好的习惯。