Bucle infinito en C/C++
loops infinite-loop (12)
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 compiladores 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)
¿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 programadores.
A todos parece gustarles 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
Según SLaks , compilan de forma idéntica.
Ben Zotto también dice que no importa :
No es más rápido. Si realmente te importa, compila 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 intento 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 <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; }
}
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.
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.
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 programadores 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 codificación 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.
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 programación que a menudo se consideran malas o descaradamente peligrosas en la programació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 programador 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 compiladores 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 programador tenía la intención de escribirwhile(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 cambiowhile(true)
. El consenso entre los programadores 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 compilación, necesitamos incluir un archivo de encabezado, que puede o no ser inconveniente. En C ++ esto no es un problema, ya quebool
existe como un tipo de datos primitivo ytrue
es una palabra clave de idioma.Otra desventaja de esta forma es que utiliza el tipo de bobina C99, que solo está disponible en compiladores 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 compilador / 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 programadores, que ni siquiera conocen las partes fundamentales del lenguaje de programació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.
Ellos son lo mismo. Pero sugiero "while (ture)" que tiene la mejor representación.
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
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 sintaxis 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 programadores que no saben for(;;)
qué for(;;)
hace (incluso si saben lo que es habitual for
ciclo), pero casi todos los programadores 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 programadores de C, escribo while(1)
, porque muchos programadores de C escriben esto y no encuentro ninguna razón para desviarse de la norma.
Probablemente compilan 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){}
.
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.
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.
Yo recomendaría while (1) { }
o while (true) { }
. Es lo que escribirían la mayoría de los programadores, 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 programadores. Pero desde el código que he visto, en C desde 1984, creo que es verdad).
Cualquier compilador razonable compilaría todos ellos en el mismo código, con un salto incondicional, pero no me sorprendería que existan algunos compiladores poco razonables , para sistemas integrados u otros sistemas especializados.