22

¿Puede la optimización del compilador eliminar bucles infinitos, lo que no cambia cualquier dato, como¿Se les permite a los compiladores eliminar infinitos bucles?

while(1) 
    /* noop */; 

Desde el análisis de un compilador gráfico de flujo de datos puede derivar, que tal bucle es "código muerto" sin ningún tipo de efectos secundarios.

¿Está borrando los bucles infinitos prohibidos por los estándares C90/C99?

¿Los estándares C90 o C99 permiten que el compilador elimine dichos bucles?

Actualización: "Microsoft C versión 6.0 hizo esencialmente esta optimización.", Consulte el enlace de caf.

label: goto label; 
return 0; 

serán transformados a

return 0; 
+2

Está ocupado esperando, y los daemons no deben usar ocupado, esperando mucho. Llamo tal construcción "muerta" en el flujo de datos. Si la instrucción no cambia ninguna variable y no contiene efectos secundarios, se puede eliminar optimizando el compilador. – osgx

+0

El código después del bucle no es "inalcanzable", el bucle se puede interrumpir con la señal, y puede haber "longjmp" en el manejador de señal. – osgx

+3

"Optimización de bucles sin fin" == "cómo hacer que bucles interminables terminen más rápido" –

Respuesta

19

C11 aclara la respuesta a esta pregunta, en el proyecto de C11 sección estándar 6.8.5declaraciones de iteración añade el apartado siguiente:

Una declaración iteración cuya expresión de control no es una constante expresión, 156) que no realiza operaciones de entrada/salida, no accede a objetos volátiles, y no realiza operaciones de sincronización en su cuerpo, control de expresión o (en el caso de para la declaración) su expresión-3, puede ser asumida por la implementación para terminar. 157)

y la nota 157 dice:

Esto tiene por objeto permitir transformaciones del compilador, como la eliminación de bucles vacías incluso cuando terminación no pueden ser probados.

Así que su ejemplo específico:

while(1) 
    /* noop */; 

no es juego limpio para la optimización ya que la expresión de control es una expresión constante.

bucles

infinito como UB

¿Por qué son los compiladores permite optimizar distancia bucles infinitos con la excepción de lo previsto anteriormente, así Hans Boehm proporciona una justificación para hacer bucles infinitos comportamiento indefinido en: N1528: Why undefined behavior for infinite loops?, la siguiente cita da una buena sensación para la edición implicada:

Como N1509 señala correctamente, el actual proyecto da esencialmente comportamiento indefinido a bucles infinitos en 6.8.5p6. Un problema importante para es que permite que el código se mueva a través de un bucle no terminante potencialmente . Por ejemplo, supongamos que tenemos los siguientes bucles, donde recuento y Cont2 son variables globales (o han tenido su dirección de tomada) y P es una variable local, cuya dirección no ha sido tomada:

for (p = q; p != 0; p = p -> next) { 
    ++count; 
} 
for (p = q; p != 0; p = p -> next) { 
    ++count2; 
} 

se pudo estos dos bucles se fusionan y se reemplazan por el siguiente bucle?

for (p = q; p != 0; p = p -> next) { 
     ++count; 
     ++count2; 
} 

Sin la dispensación especial en 6.8.5p6 de bucles infinitos, esto sería no permitidos: Si el primer bucle no termina porque q apunta a una lista circular, el original nunca se escribe en Cont2. Por lo tanto, podría ejecutarse en paralelo con otro subproceso que tenga acceso o actualizaciones count2. Esto ya no es seguro con la versión transformada que accede a count2 a pesar del bucle infinito. Por lo tanto, la transformación potencialmente introduce una carrera de datos.

En casos como este, es muy poco probable que un compilador pueda para probar la terminación del bucle; tendría que entender que q los puntos de a una lista acíclica, que creo que está más allá de la capacidad de la mayoría de los compiladores mainstream, y a menudo imposible sin toda la información del programa .

C99

Desde C99 no tiene esta tallar, miraríamos a la como-si la regla trata en la sección 5.1.2.3 que básicamente dice que el compilador sólo tiene que emular el comportamiento observable de un programa, los requisitos son los siguientes:

los requisitos mínimos en una aplicación conforme son:

  • En los puntos de secuencia, los objetos volátiles son estables en el sentido de que los accesos anteriores son completos y los accesos subsiguientes aún no han ocurrido.
  • Al finalizar el programa, todos los datos escritos en los archivos deben ser idénticos al resultado que la ejecución del programa según la semántica abstracta hubiera producido .
  • La dinámica de entrada y salida de los dispositivos interactivos se realizará como se especifica en 7.19.3. La intención de estos requisitos es que la salida sin buffer o con buffer de línea aparezca tan pronto como sea posible, para garantizar que los mensajes de aviso aparezcan realmente antes de un programa que espera la entrada.

Una lectura estricta de este parecería permitir que una aplicación para optimizar un bucle infinito de distancia. Desde luego, podemos llegar a escenarios en los que la optimización de distancia en un bucle infinito causaría un cambio en la conducta observable:

while(1) ; 
printf("hello world\n") ; 

Muchos dirán que efectuar la terminación de un proceso es también el comportamiento observable, esta posición se toma en C Compilers Disprove Fermat’s Last Theorem:

el compilador se da una gran libertad en la forma en que implementa el programa en C, pero su producción debe tener el mismo comportamiento visible externamente que el programa tendría si se interpreta por la “máquina abstracta C” que se describe en la norma . Muchas personas conocedoras (incluyéndome a mí) leen esto diciendo que el comportamiento de terminación de un programa no debe cambiarse. Obviamente, algunos escritores de compiladores no están de acuerdo, o de lo contrario no creen que importe. El hecho de que personas razonables no estén de acuerdo con la interpretación parece indicar que el estándar C es defectuoso.

actualización

de alguna manera se perdió el seguimiento con el artículo anterior, Compilers and Termination Revisited que dice lo siguiente acerca de la sección 5.1.2.3:

El segundo requisito es el asunto difícil.Si se trata de la terminación del programa que se ejecuta en la máquina abstracta, entonces se cumple de manera imprecisa porque nuestro programa no finaliza. Si se trata de la terminación del programa real generado por el compilador, entonces la implementación C tiene errores porque los datos escritos en los archivos (stdout es un archivo) difieren de los datos escritos por la máquina abstracta. (Esta lectura se debe a Hans Boehm, que había dejado de burlarse de esta sutileza fuera de la norma.)

Se podría también hacer un argumento más débil que la necesidad de crear un labrarse en C11 para permitir la remoción de bucle vacío implica que esto no era una optimización permitida previamente.

¿Esto se aplica a infinitos goto loops también?

Creo que la intención es que esto también se aplique a infinitos goto loops también. En C++ esto es claramente el caso desde la sección 1.10[intro.multithread] dice:

La implementación puede asumir que cualquier tema con el tiempo se lleve a cabo uno de los siguientes

  • terminar,
  • hacer una llamada a una función de E/S de la biblioteca,
  • acceder o modificar un objeto volátil, o
  • realizar una operación de sincronización o una en operación ómica

y luego la intención expresada en N1528 es que el C y C++ estándar de acuerdo:

Desde compilador back-ends se comparten típicamente entre los compiladores de C y C++, parece más importante que WG14 y WG21 acuerdan cualquier solución que se adopte. Las alternativas serían un tratamiento especial de los dos idiomas por parte del back-end o deshabilitar las optimizaciones al procesar el código C. Ninguno de los dos parece deseable.

y al final dice:

WG21 está considerando la mejora de redacción que hace que el tratamiento coherente. Es de esperar que WG14 rastree los cambios resultantes.

Actualmente el estándar C11 no contiene el texto similar en la sección 5.1.2.4ejecuciones multi-hilo y razas de datos pero teniendo en cuenta N1528 parece prudente asumir compiladores tratar infinita bucles Goto comportamiento como indefinido en C y C++.

Tenga en cuenta también ver US comment 38 here y N3196 que es el papel que este cambio se aplicó.

+1

Parece que la redacción de C11 implica que 'while (1,1)/* no-op * /; * * puede * optimizarse, ya que la introducción de un operador de coma significa que ya no es una expresión constante. – caf

+0

@caf Acepto * Las expresiones constantes no deben contener asignación, incremento, decremento, llamada a función, o operadores de coma * –

+0

¿Qué tal 'bucles'? –

3

Ellos son una necesidad cuando se escribe demonios. ¿Por qué quieres llamarlos código muerto?

+5

Eso no sería un daemon realmente agradable desde entonces (1); técnicamente no pone ese hilo a dormir y es muy intensivo en el uso del procesador. Mejor sería algo así como (1) sueño (5000); o algo similar – Toad

+0

Eso es un problema lateral. En cualquier caso, 'while (1)' es cualquier cosa menos _dead code_ - que fue mi punto. Y sí, creo que vi una herramienta de análisis de código informarlo como tal, un ciclo infinito. – dirkgently

+2

Pero 'while (1);' * es * código esencialmente inactivo; no hace nada útil excepto bloquear la CPU. Es mucho mejor tener algo como: char exit = 0; while (! Exit) { exit = process_event(); } – TMN

8

El bucle no es un código muerto, básicamente impide que el programa llegue a lo que viene después. Esto no es lo que sucedería si se elimina el bucle, por lo que el compilador no puede eliminar el bucle.

Podría reemplazarlo con una instrucción inactiva dependiente de la plataforma para indicarle al procesador que el hilo ya no va a hacer nada más.

Lo que el compilador puede hacer es eliminar cualquier código que venga después del bucle, porque es inalcanzable y nunca se ejecutará.

9

No hay manera de detectar bucles infinitos universalmente: vea the Halting Problem. Entonces, lo mejor que cualquier compilador podría hacer es tomar una estimación decente, por ejemplo, el caso obvio mencionado en el PO.

¿Pero por qué sería esto deseable? Pude ver emitir una advertencia y aún permitir el comportamiento, pero eliminar el bucle no es una "optimización", ¡cambia el comportamiento del programa!

+2

La primera regla del problema de detención es que nadie menciona el problema de detención. –

+7

Si bien es imposible para programas arbitrarios, sin duda es posible para bucles suficientemente triviales como este. – fennec

4

Esto se ha discutido muchas veces antes en comp.lang.c (por ejemplo, here) sin, hasta donde yo sé, ningún resultado de consenso.

+0

¡Gracias! ¿Puedes encontrar algunos enlaces más a tales hilos en usenet? – osgx

+8

Estoy seguro de que puedes usar google tan bien como puedo. – caf

0

Creo que las normas más recientes establecen explícitamente que si un fragmento de código no accede a variables volátiles, realiza E/S, etc. cualquier otro código que no dependa de nada computado en la primera pieza puede ser secuenciado arbitrariamente eso. Si un bucle infinito no realiza ninguna E/S, ni calcula ningún valor que se utilice más adelante en el programa, un compilador puede simplemente diferir la ejecución del bucle hasta que todo lo demás se haya completado.

+0

Puedes creer incluso en Dios, pero no puede haber un estándar ISO del comportamiento de Dios. ¿Hay stadards de lenguajes tipo C que lo permitan? – osgx

+1

@osgx: Considere el código "int main (void) {do_something(); do_something_else(); return 0;}" Supongamos que el compilador puede ver el código y determinar que do_something() no escribe ninguna variable volátil, ni lo hace escribe cualquier variable que do_something_else() alguna vez va a usar. Lo único que do_something() podría hacer que alteraría la salida de do_something_else() sería infinite-loop. Si a un compilador solo se le permitiera soltar el código que no pudiera repetirse infinitamente, se lo obligaría a incluir muchos códigos inútiles. Permitir que se eliminen bucles infinitos facilita las cosas. – supercat

+0

c99 5.1.2.3 "Acceder a un objeto volátil, modificar un objeto, modificar un archivo o llamar a una función que realiza alguna de esas operaciones" - es el único efecto secundario. Ellos "cambian el estado del entorno de ejecución". "Una implementación real de no necesita evaluar parte de una expresión si puede deducir que su valor no se usa y que no se producen efectos secundarios necesarios" – osgx

Cuestiones relacionadas