Bucle infinito en C/C++




loops infinite-loop (8)

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.


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 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 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 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 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.


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.


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.





infinite-loop