Bucle infinito en C / C ++

Hay varias posibilidades para hacer un ciclo sin fin, aquí hay algunas que elegiría:

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

¿Hay alguna forma determinada que uno debería elegir? ¿Y los comstackdores modernos hacen una diferencia entre el enunciado medio y el último o se da cuenta de que se trata de un ciclo sin fin y omite por completo la parte de verificación?

Editar: como se ha mencionado, olvidé goto , pero esto se hizo por la razón de que no me gusta como un comando en absoluto.

Edit2: hice un poco de grep en las últimas versiones tomadas de kernel.org. Parece que nada ha cambiado mucho con el tiempo (al menos en el kernel) enter image description here

El problema al hacer esta pregunta es que obtendrá tantas respuestas subjetivas que simplemente dice “Prefiero esto …”. En lugar de hacer declaraciones tan inútiles, trataré de responder esta pregunta con hechos y referencias, en lugar de opiniones personales.

A través de la experiencia, probablemente podamos comenzar excluyendo las alternativas do-while (y el goto), ya que no se usan comúnmente. No recuerdo haberlos visto en el código de producción en vivo, escrito por profesionales.

El while(1) , while(true) y for(;;) son las 3 versiones diferentes que existen comúnmente en el código real. Por supuesto, son completamente equivalentes y dan como resultado el mismo código de máquina.


for(;;)

  • Este es el ejemplo canónico original de un ciclo eterno. En la antigua C Biblia The C Programming Language de Kernighan y Ritchie, podemos leer que:

    K & R 2nd ed 3.5:

     for (;;) { ... } 

    es un ciclo “infinito”, presumiblemente roto por otros medios, como una ruptura o retorno. Ya sea para usar while o for es en gran parte una cuestión de preferencia personal.

    Durante mucho tiempo (pero no para siempre), este libro fue considerado como el canon y la definición misma del lenguaje C. Dado que K & R decidió mostrar un ejemplo de for(;;) , esto se habría considerado como la forma más correcta al menos hasta la estandarización C en 1990.

    Sin embargo, K & R ya declararon que era una cuestión de preferencia.

    Y hoy, K & R es una fuente muy cuestionable para usar como referencia canónica de C. No solo está desactualizado varias veces (no se dirige a C99 ni a C11), también predica prácticas de progtwigción que a menudo se consideran malas o descaradamente peligrosas en la progtwigción moderna de C.

    Pero a pesar de que K & R es una fuente cuestionable, este aspecto histórico parece ser el argumento más fuerte a favor del for(;;) .

  • El argumento en contra del bucle for(;;) es que es algo oscuro e ilegible. Para comprender lo que hace el código, debe conocer la siguiente regla del estándar:

    ISO 9899: 2011 6.8.5.3:

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

    / – /

    Tanto la cláusula-1 como la expresión-3 pueden omitirse. Una expresión-2 omitida se reemplaza por una constante distinta de cero.

    Basándome en este texto del estándar, creo que la mayoría estará de acuerdo en que no solo es oscuro, también es sutil, ya que la primera y la tercera parte del bucle for se tratan de manera diferente que el segundo, cuando se omite.


while(1)

  • Esta es supuestamente una forma más legible que for(;;) . Sin embargo, se basa en otra regla oscura, aunque bien conocida, a saber, que C trata a todas las expresiones distintas de cero como lógicas lógicas booleanas. Cada progtwigdor de C es consciente de eso, por lo que no es probable que sea un gran problema.

  • Hay un gran problema práctico con esta forma, a saber, que los comstackdores tienden a darle una advertencia: “la condición siempre es verdadera” o similar. Esa es una buena advertencia, de un tipo que realmente no desea deshabilitar, porque es útil para encontrar varios errores. Por ejemplo, un error como while(i = 1) , cuando el progtwigdor tenía la intención de escribir while(i == 1) .

    Además, es probable que los analizadores de códigos estáticos externos gimen sobre “la condición siempre es cierta”.


while(true)

  • Para hacer while(1) aún más legible, algunos lo usan en cambio while(true) . El consenso entre los progtwigdores parece ser que esta es la forma más legible.

  • Sin embargo, este formulario tiene el mismo problema que while(1) , como se describe arriba: las advertencias de “la condición siempre es verdadera”.

  • Cuando se trata de C, esta forma tiene otra desventaja, es decir, que utiliza la macro true de stdbool.h. Entonces, para hacer esta comstackción, necesitamos incluir un archivo de encabezado, que puede o no ser inconveniente. En C ++ esto no es un problema, ya que bool existe como un tipo de datos primitivo y true es una palabra clave de idioma.

  • Otra desventaja de esta forma es que utiliza el tipo de bobina C99, que solo está disponible en comstackdores modernos y no es compatible con versiones anteriores. De nuevo, esto es solo un problema en C y no en C ++.


Entonces, ¿qué forma de usar? Ninguno parece perfecto. Es, como ya dijo K & R en las edades oscuras, una cuestión de preferencia personal.

Personalmente, siempre uso for(;;) solo para evitar las advertencias de comstackdor / analizador frecuentemente generadas por las otras formas. Pero quizás más importante debido a esto:

Si incluso un principiante de C sabe que for(;;) significa un ciclo eterno, entonces ¿para quién está tratando de hacer que el código sea más legible?

Supongo que eso es a lo que todo se reduce. Si intenta hacer que su código fuente sea legible para los no progtwigdores, que ni siquiera conocen las partes fundamentales del lenguaje de progtwigción, solo están perdiendo el tiempo. No deberían leer tu código.

Y dado que todos los que deberían leer su código ya saben qué for(;;) , no tiene sentido hacerlo más legible, ya es lo más legible posible.

Es muy subjetivo Yo escribo esto:

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

Porque su intención es muy clara y también es legible: la miras y sabes que se pretende un ciclo infinito.

Se podría decir for(;;) también está claro. Pero yo diría que debido a su syntax intrincada , esta opción requiere conocimiento adicional para llegar a la conclusión de que se trata de un ciclo infinito, por lo tanto, es relativamente menos claro. Incluso diría que hay más progtwigdores que no saben for(;;) qué for(;;) hace (incluso si saben lo que es habitual for ciclo), pero casi todos los progtwigdores que saben while lo hacen inmediatamente se dan cuenta de lo while(true) .

Para mí, escribir for(;;) para significar bucle infinito, es como escribir while() para significar bucle infinito – mientras que el primero funciona, el último no. En el primer caso, la condición de vacío resulta ser true implícitamente, pero en el último caso, ¡es un error! Personalmente no me gustó.

Ahora, while(1) también está allí en la competencia. Preguntaría: ¿por qué while(1) ? ¿Por qué no while(2) , while(3) o while(0.1) ? Bueno, sea lo que sea que escribas, en realidad quieres decir while(true) , si es así, ¿por qué no escribirlo?

En C (si alguna vez escribo), probablemente escribiría esto:

 while(1) {} //in C 

Mientras que while(2) , while(3) y while(0.1) también tendrían sentido. Pero solo para estar conforme con otros progtwigdores de C, escribo while(1) , porque muchos progtwigdores de C escriben esto y no encuentro ninguna razón para desviarse de la norma.

En un último acto de aburrimiento, en realidad escribí algunas versiones de estos loops y los compilé con GCC en mi mac mini.

while(1){} y for(;;) {} produjeron los mismos resultados de ensamblaje mientras que do{} while(1); producido código de ensamblaje similar pero diferente

aquí está el de while / for loop

  .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 

y el bucle 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 

A todos parece gustarles 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

Según SLaks , comstackn de forma idéntica.

Ben Zotto también dice que no importa :

No es más rápido. Si realmente te importa, comstack con la salida del ensamblador para tu plataforma y mira para ver. No importa. Esto nunca importa. Escribe tus bucles infinitos como quieras.

En respuesta al usuario1216838, aquí está mi bash de reproducir sus resultados.

Aquí está mi máquina:

 cat /etc/*-release CentOS release 6.4 (Final) 

versión de gcc:

 Target: x86_64-unknown-linux-gnu Thread model: posix gcc version 4.8.2 (GCC) 

Y prueba los archivos:

 // testing.cpp #include  int main() { do { break; } while(1); } // testing2.cpp #include  int main() { while(1) { break; } } // testing3.cpp #include  int main() { while(true) { break; } } 

Los comandos:

 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 

La única diferencia es la primera línea, también conocido como el nombre del archivo.

 test1.asm test2.asm differ: byte 16, line 1 

Salida:

  .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 

Con -O3 , la salida es considerablemente más pequeña, por supuesto, pero todavía no hay diferencia.

El modismo diseñado en el lenguaje C (y heredado en C ++) para el bucle infinito es for(;;) : la omisión de un formulario de prueba. Los ciclos do/while y while no tienen esta característica especial; sus expresiones de prueba son obligatorias.

for(;;) no expresa “bucle mientras que alguna condición es verdadera que siempre es verdadera”. Expresa “loop infinitamente”. Ninguna condición superflua está presente.

Por lo tanto, el constructo for(;;) es el bucle infinito canónico . Esto es un hecho.

Lo único que se deja a la opinión es si escribir o no el bucle infinito canónico, o elegir algo barroco que involucre identificadores y constantes adicionales, para construir una expresión superflua.

Incluso si la expresión de prueba de while fuera opcional, lo cual no es, while(); sería extraño while que? Por el contrario, la respuesta a la pregunta ¿ for qué? es: por qué, siempre — ¡para siempre! Como broma, algunos progtwigdores de días pasados ​​han definido macros en blanco, por lo que podrían escribir for(ev;e;r); .

while(true) es superior a while(1) porque al menos no implica el kludge que 1 representa la verdad. Sin embargo, while(true) no entró en C hasta C99. for(;;) existe en cada versión de C que se remonta al lenguaje descrito en el libro de 1978 K & R1, y en cada dialecto de C ++, e incluso en los idiomas relacionados. Si está codificando en una base de código escrita en C90, debe definir su propia true para while (true) .

while(true) lee mal. Mientras que lo que es verdad? Realmente no queremos ver el identificador true en el código, excepto cuando estamos inicializando variables booleanas o asignándolas. true no necesita aparecer en las pruebas condicionales. Un buen estilo de encoding evita cruft como este:

 if (condition == true) ... 

a favor de:

 if (condition) ... 

Por esta razón, while (0 == 0) es superior a while (true) : utiliza una condición real que prueba algo, que se convierte en una oración: “bucle mientras que cero es igual a cero”. Necesitamos un predicado que vaya bien con “mientras”; la palabra “verdadero” no es un predicado, pero el operador relacional == es.

Probablemente comstackn hasta casi el mismo código de máquina, por lo que es una cuestión de gusto.

Personalmente, elegiría el que es el más claro (es decir, muy claro que se supone que es un ciclo infinito).

Me inclinaría hacia while(true){} .

Uso for(;/*ever*/;) .

Es fácil de leer y tarda un poco más en escribir (debido a los cambios de los asteriscos), lo que indica que debo tener mucho cuidado al usar este tipo de ciclo. El texto verde que aparece en el condicional también es una vista bastante extraña, otra indicación de que este constructo está mal visto a menos que sea absolutamente necesario.

¿Hay alguna forma determinada que uno debería elegir?

Puedes elegir cualquiera. Su materia de elección. Todos son equivalentes. while(1) {}/while(true){} se usa frecuentemente para bucle infinito por los progtwigdores.

Bueno, hay mucho gusto en este. Creo que es más probable que las personas de un entorno C prefieran (;;), que dice “para siempre”. Si es por trabajo, haz lo que hacen los lugareños, si es para ti, haz lo que puedas leer más fácilmente.

Pero en mi experiencia, haz {} mientras (1); casi nunca se usa.

Yo recomendaría while (1) { } o while (true) { } . Es lo que escribirían la mayoría de los progtwigdores, y por razones de legibilidad debes seguir las expresiones comunes.

(Bien, entonces hay una “cita necesaria” para el reclamo sobre la mayoría de los progtwigdores. Pero desde el código que he visto, en C desde 1984, creo que es verdad).

Cualquier comstackdor razonable comstackría todos ellos en el mismo código, con un salto incondicional, pero no me sorprendería que existan algunos comstackdores poco razonables , para sistemas integrados u otros sistemas especializados.

Ellos son lo mismo. Pero sugiero “while (ture)” que tiene la mejor representación.

Todos van a realizar la misma función, y es verdad elegir lo que prefiera … podría pensar “while (1) o while (true)” es una buena práctica para usar.