taxis taxi madrid huelga barcelona c infinite-loop undefined-behavior

taxi - Es tiempo(1); comportamiento indefinido en C?



huelga de taxis barcelona 2018 (4)

En C ++ 11 es un comportamiento indefinido , pero es el caso en C que while(1); es el comportamiento indefinido?


Después de verificar el borrador de la norma C99 , diría "no", no está indefinido. No puedo encontrar ningún idioma en el borrador que mencione el requisito de que las iteraciones terminen.

El texto completo del párrafo que describe la semántica de las afirmaciones iterativas es:

Una instrucción de iteración hace que una instrucción llamada el cuerpo del bucle se ejecute repetidamente hasta que la expresión de control se compare igual a 0.

Espero que aparezca alguna limitación, como la especificada para C ++ 11, si corresponde. También hay una sección llamada "Restricciones", que tampoco menciona ninguna restricción de este tipo.

Por supuesto, el estándar real podría decir algo más, aunque lo dudo.


Es un comportamiento bien definido. En C11 se ha añadido una nueva cláusula 6.8.5 ad 6.

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

157) Esto tiene la intención de permitir transformaciones del compilador, como la eliminación de bucles vacíos, incluso cuando no se puede probar la terminación.

Dado que la expresión de control de su bucle es una constante, el compilador no puede asumir que el bucle termina. Está destinado a programas reactivos que deberían ejecutarse para siempre, como un sistema operativo.

Sin embargo, para el siguiente bucle el comportamiento no está claro.

a = 1; while(a);

En efecto, un compilador puede o no eliminar este bucle, dando como resultado un programa que puede terminar o no terminar. Eso no es realmente indefinido, ya que no está permitido borrar su disco duro, pero es una construcción para evitar.

Sin embargo, hay otro inconveniente, considere el siguiente código:

a = 1; while(a) while(1);

Ahora, dado que el compilador puede asumir que el bucle externo termina, el bucle interno también debería terminar, ¿de qué otra manera podría terminar el bucle externo? Así que si tienes un compilador realmente inteligente entonces un while(1); el bucle que no debería terminar tiene que tener tales bucles no terminantes alrededor hasta el main . Si realmente quieres el bucle infinito, será mejor que leas o escribas alguna variable volatile en él.

¿Por qué esta cláusula no es práctica?

Es muy poco probable que nuestra compañía de compiladores vaya a hacer uso de esta cláusula, principalmente porque es una propiedad muy sintáctica. En la representación intermedia (IR), la diferencia entre la constante y la variable en los ejemplos anteriores se pierde fácilmente a través de la propagación constante.

La intención de la cláusula es permitir que los escritores de compiladores apliquen transformaciones deseables como las siguientes. Considere un bucle no tan infrecuente:

int f(unsigned int n, int *a) { unsigned int i; int s; s = 0; for (i = 10U; i <= n; i++) { s += a[i]; } return s; }

Por razones arquitectónicas (por ejemplo, bucles de hardware) nos gustaría transformar este código en:

int f(unsigned int n, int *a) { unsigned int i; int s; s = 0; for (i = 0; i < n-9; i++) { s += a[i+10]; } return s; }

Sin la cláusula 6.8.5 ad 6, esto no es posible, porque si n es igual a UINT_MAX , el bucle no puede terminar. Sin embargo, es bastante claro para un humano que esta no es la intención del autor de este código. La cláusula 6.8.5 ad 6 ahora permite esta transformación. Sin embargo, la forma en que esto se logra no es muy práctico para un compilador, ya que el requisito sintáctico de un bucle infinito es difícil de mantener en el IR.

Tenga en cuenta que es esencial que n e i unsigned estén unsigned porque el desbordamiento en signed int da un comportamiento indefinido y, por lo tanto, la transformación puede justificarse por este motivo. Sin embargo, un código eficiente se beneficia del uso unsigned , aparte del rango positivo más grande.

Un enfoque alternativo

Nuestro enfoque sería que el escritor de códigos debe expresar su intención, por ejemplo, insertando un assert(n < UINT_MAX) antes del bucle o alguna garantía de Frama-C. De esta manera, el compilador puede "probar" la terminación y no tiene que depender de la cláusula 6.8.5 ad 6.

PD: Estoy viendo un borrador del 12 de abril de 2011, ya que paxdiablo está mirando claramente una versión diferente, tal vez su versión sea más nueva. En su cita no se menciona el elemento de expresión constante.


La respuesta más simple implica una cita de §5.1.2.3p6, que establece los requisitos mínimos de una implementación conforme:

Los requisitos mínimos para una implementación conforme son:

- Los accesos a objetos volátiles se evalúan estrictamente de acuerdo con las reglas de la máquina abstracta.

- Al finalizar el programa, todos los datos escritos en los archivos serán idénticos al resultado que la ejecución del programa de acuerdo con la semántica abstracta hubiera producido.

- La dinámica de entrada y salida de los dispositivos interactivos se llevará a cabo como se especifica en 7.21.3. La intención de estos requisitos es que la salida sin búfer o con búfer de línea aparezca tan pronto como sea posible, para garantizar que los mensajes de solicitud realmente aparezcan antes de que un programa esté esperando la entrada.

Este es el comportamiento observable del programa.

Si el código de la máquina no produce el comportamiento observable debido a las optimizaciones realizadas, entonces el compilador no es un compilador de C. ¿Cuál es el comportamiento observable de un programa que contiene solo un bucle infinito en el punto de terminación? La única manera en que un bucle de este tipo podría terminar es mediante una señal que haga que termine prematuramente. En el caso de SIGTERM , el programa termina. Esto no causaría un comportamiento observable. Por lo tanto, la única optimización válida de ese programa es el compilador que se adelanta al sistema cerrando el programa y generando un programa que termina inmediatamente.

/* unoptimised version */ int main() { for (;;); puts("The loop has ended"); } /* optimised version */ int main() { }

Una posibilidad es que se genere una señal y se llame a longjmp para hacer que la ejecución salte a una ubicación diferente. Parece que el único lugar al que se podría saltar es en algún lugar al que se haya llegado durante la ejecución antes del bucle, por lo que si el compilador es lo suficientemente inteligente como para notar que se genera una señal que provoca que la ejecución salte a otro lugar, podría optimizar el bucle (y el aumento de la señal) de distancia en favor de saltar de inmediato.

Cuando varios subprocesos ingresan a la ecuación, una implementación válida podría transferir la propiedad del programa desde el subproceso principal a un subproceso diferente y finalizar el subproceso principal. El comportamiento observable del programa aún debe ser observable, independientemente de las optimizaciones.


La siguiente declaración aparece en C11 6.8.5 Iteration statements /6 :

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

Desde entonces while(1); utiliza una expresión constante, la implementación no puede asumir que terminará.

Un compilador es libre de eliminar dicho bucle por completo si la expresión no es constante y todas las demás condiciones se cumplen de manera similar, incluso si no se puede probar de manera concluyente que el bucle terminaría.