c++ c divide-by-zero compile-time-constant constant-expression

c++ - Dividiendo por cero en una expresión constante.



divide-by-zero compile-time-constant (5)

Mi compilador de juguete se bloquea si divido por cero en una expresión constante:

int x = 1 / 0;

¿Está permitido este comportamiento por los estándares C y / o C ++?


Cómo se debe comportar el compilador no está relacionado con el valor de la expresión. El compilador no debería fallar. Período.

Me imagino que una implementación pedante, dada una expresión como esta, se compilaría con un código que se ejecutará 1/0 en el tiempo de ejecución, pero no creo que se vea como una buena característica.

Por lo tanto, el espacio restante es que el compilador debe declinar compilarlo y tratarlo como una clase de error de código fuente.


Del proyecto de norma C (N1570):

6.5.5 Operadores multiplicativos

...

  1. El resultado del operador / es el cociente de la división del primer operando por el segundo; El resultado del operador% es el resto. En ambas operaciones, si el valor del segundo operando es cero, el comportamiento no está definido.

Y sobre el comportamiento indefinido en el capítulo 3. Términos, definiciones y símbolos:

3.4.3

  1. comportamiento indefinido
    comportamiento, en el uso de una construcción de programa no portátil o errónea o de datos erróneos, para los cuales esta Norma Internacional no impone requisitos
  2. NOTA El posible comportamiento indefinido abarca desde ignorar la situación completamente con resultados impredecibles, hasta comportarse durante la traducción o la ejecución del programa de una manera documentada característica del entorno (con o sin la emisión de un mensaje de diagnóstico) , hasta terminar una traducción o ejecución (con la emisión de un mensaje de diagnóstico).

Así que se permite estrellar el compilador.


La mera presencia de 1/0 no permite que el compilador se bloquee. A lo sumo, se permite suponer que la expresión nunca se evaluará y, por lo tanto, esa ejecución nunca alcanzará la línea dada.

Si se garantiza la evaluación de la expresión, el estándar no impone requisitos en el programa o compilador. Entonces el compilador puede fallar.

1/0 es solo UB si se evalúa.

El estándar C11 proporciona un ejemplo explícito de 1/0 como comportamiento definido cuando no se evalúa:

Así, en la siguiente inicialización,

static int i = 2 || 1 / 0;

la expresión es una expresión constante de entero válida con valor uno.

Artículo 6.6, nota 118.

1/0 no es una expresión constante.

La sección 6.6 de la norma C11, bajo Restricciones, dice

  1. Las expresiones constantes no contendrán operadores de asignación, incremento, decremento, función-llamada o coma, excepto cuando estén contenidas dentro de una subexpresión que no se evalúa.
  2. Cada expresión constante se evaluará como una constante que esté en el rango de valores representables para su tipo.

Como 1/0 no se evalúa como una constante en el rango de valores representables por un int, 1/0 no es una expresión constante. Esta es una regla sobre lo que cuenta como una expresión constante, como la regla sobre no tener asignaciones en ella. Puede ver que al menos para C ++, Clang no considera 1/0 una expresión constante :

prog.cc:3:18: error: constexpr variable ''x'' must be initialized by a constant expression constexpr int x = 1/ 0 ; ^ ~~~~

No tendría mucho sentido que un 1/0 no evaluado fuera UB.

(x == 0) ? x : 1 / x (x == 0) ? x : 1 / x está perfectamente bien definido, incluso si x es 0 y la evaluación de 1 / x es UB. Si fuera el caso que (0 == 0) ? 0 : 1 / 0 (0 == 0) ? 0 : 1 / 0 1/0 eran UB, eso sería una tontería.


Otros ya han mencionado el texto relevante de las normas, así que no voy a repetir eso.

La función de evaluación de la expresión del compilador de C toma una expresión en Notación polaca inversa (matriz de valores (números e identificadores) y operadores) y devuelve dos cosas: una marca para determinar si la expresión se evalúa como una constante y el valor si es una constante ( 0 de lo contrario). Si el resultado es una constante, todo el RPN se reduce a esa constante. 1/0 no es una expresión constante, ya que no se evalúa como un valor entero constante. El RPN no se reduce para 1/0 y permanece intacto.

En C, las variables estáticas se pueden inicializar solo con valores constantes. Entonces, el compilador falla cuando ve que un inicializador para una variable estática no es una constante. Las variables de almacenamiento automático pueden inicializarse con expresiones no constantes. En este caso, mi compilador genera código para evaluar 1/0 (¡todavía tiene el RPN para esta expresión!). Si se alcanza este código en tiempo de ejecución, UB se produce según lo prescrito por los estándares de idioma. [En x86, este UB toma la forma de la excepción de CPU por división, mientras que en MIPS este UB produce un valor de cociente incorrecto (la CPU no tiene una excepción de división por cero).]

Mi compilador soporta correctamente el cortocircuito en las expresiones ||-y las expresiones &&-. Entonces, evalúa 1 || 1/0 1 || 1/0 como 1 y 0 && 1/0 como 0, independientemente de si el operando de la derecha del operador lógico es una constante o no. La función de evaluación de expresiones elimina los operandos de la derecha de estos operadores (junto con los operadores) cuando no deben evaluarse, por lo que 1 || 1/0 1 || 1/0 transforma en 1 != 0 (recuerde que los operandos de && y || se comparan con 0), que produce 1 y 0 && 1/0 transforma en 0 != 0 , que da 0.

Otro caso a tener en cuenta es INT_MIN / -1 e INT_MIN % -1 (ídem para tipos de enteros más grandes). El cociente no se puede representar como un int firmado (en el caso de los enteros con signo del complemento de 2, que es el que tenemos en todas las CPU modernas) y, por lo tanto, esto también es UB (obtienes la misma división por excepción de cero en x86 en tiempo de ejecución) . Yo manejo este caso de manera similar. Esta expresión no puede inicializar una variable de almacenamiento estático y se tira si no se evalúa en la lógica && / || operador. Puede inicializar una variable automática, posiblemente llevando a UB en tiempo de ejecución.

También emito una advertencia cuando se encuentra dicha división.


Sí, la división por cero es un comportamiento indefinido y ni el estándar C ni el C ++ imponen ningún requisito en tales casos. Aunque en este caso creo que debería al menos emitir un diagnóstico ( ver más abajo ).

Antes de citar los estándares, debo señalar que, aunque esto puede ser un comportamiento acorde, la calidad de la implementación es un problema diferente, el simple hecho de cumplir no es lo mismo que ser útil. Por lo que sé, el equipo de gcc, clang, Visual Studio e Intel ( según tpg2114 ) considera que los errores internos del compilador ( ICE ) son errores que deben informarse. Se debe tener en cuenta que tanto el gcc actual como el clang producen una advertencia para este caso aparentemente sin importar las banderas provistas. En el caso donde ambos operandos son literales / constantes, el caso que tenemos aquí, parece bastante sencillo detectar y proporcionar un diagnóstico para esto. clang produce el siguiente diagnóstico para este caso ( véalo en vivo ):

warning: division by zero is undefined [-Wdivision-by-zero] int x = 1 / 0 ; ^ ~

Del borrador de la sección C11, sección 6.5.5 Operadores multiplicativos (el énfasis es mío ):

El resultado del operador / es el cociente de la división del primer operando por el segundo; [...] si el valor del segundo operando es cero, el comportamiento no está definido.

Y así es un comportamiento indefinido.

El borrador del estándar de C ++, sección 5.6 [expr.mul] dice:

El operador binario produce el cociente [...] Si el segundo operando de / o% es cero, el comportamiento no está definido [...]

De nuevo el comportamiento indefinido.

Tanto el borrador de la norma de C ++ como el borrador de la norma C tienen una definición similar para el comportamiento indefinido que dice:

[...] para los cuales esta Norma Internacional no impone requisitos

La frase no impone requisitos parece demasiado permitir cualquier comportamiento, incluidos los demonios nasales . Ambos tienen una nota similar que dice algo en la línea de:

Se puede esperar un comportamiento indefinido cuando esta Norma Internacional omite cualquier definición explícita de comportamiento o cuando un programa utiliza una construcción errónea o datos erróneos. El comportamiento indefinido permisible va desde ignorar la situación completamente con resultados impredecibles , a comportarse durante la traducción o la ejecución del programa de una manera documentada característica del entorno (con o sin la emisión de un mensaje de diagnóstico), hasta terminar una traducción o ejecución (con la emisión de un mensaje de diagnóstico) .

Entonces, aunque las notas no son normativas, parece que si va a terminar durante la traducción, al menos debe emitir un diagnóstico. El término terminación no está definido, por lo que es difícil argumentar lo que esto permite. No creo haber visto un caso en el que clang y gcc tengan un ICE sin un diagnóstico.

¿El código tiene que ser ejecutado?

Si leemos ¿Puede el código que nunca se ejecutará invocar un comportamiento indefinido? podemos ver que al menos en el caso de C hay espacio para el debate donde se debe ejecutar el 1/0 para invocar un comportamiento indefinido. Lo que es peor en el caso de C ++, la definición de comportamiento no está presente, por lo que parte del análisis utilizado para el caso de C ++ no se puede usar para el caso de C ++.

Parece que si el compilador puede probar que el código nunca se ejecutará, podemos razonar que sería como si el programa no tuviera un comportamiento indefinido, pero no creo que esto sea demostrable, solo un comportamiento razonable.

Desde la perspectiva C, el informe de defectos del WG14 109 lo aclara aún más. Se da el siguiente ejemplo de código:

int foo() { int i; i = (p1 > p2); /* Must this be "successfully translated"? */ 1/0; /* Must this be "successfully translated"? */ return 0; }

y la respuesta incluyó:

Además, si cada ejecución posible de un programa dado resultara en un comportamiento indefinido, el programa dado no es estrictamente conforme.
Una implementación conforme no debe fallar en traducir un programa estrictamente conforme simplemente porque alguna posible ejecución de ese programa resultaría en un comportamiento indefinido. Debido a que foo nunca podría ser llamado, el ejemplo dado debe ser traducido exitosamente por una implementación conforme.

Entonces, en el caso de C, a menos que se pueda garantizar que se ejecutará el código que invoca un comportamiento indefinido, el compilador debe traducir el programa correctamente.

C ++ caso constexpr

Si x era una variable constexpr:

constexpr int x = 1 / 0 ;

estaría mal formado y gcc produce una advertencia y clang hace que sea un error ( véalo en vivo ):

error: constexpr variable ''x'' must be initialized by a constant expression constexpr int x = 1/ 0 ; ^ ~~~~ note: division by zero constexpr int x = 1/ 0 ; ^ warning: division by zero is undefined [-Wdivision-by-zero] constexpr int x = 1/ 0 ; ^ ~

Es útil señalar que la división por cero no está definida .

El borrador de la sección 5.19 C ++ estándar Expresiones constantes [expr.const] dice:

Una expresión condicional e es una expresión constante central a menos que la evaluación de e, siguiendo las reglas de la máquina abstracta (1.9), evalúe una de las siguientes expresiones

e incluye la siguiente viñeta:

una operación que tendría un comportamiento indefinido [Nota: incluyendo, por ejemplo, desbordamiento de entero con signo (Cláusula 5), ​​cierta aritmética de punteros (5.7), división por cero (5.6), o ciertas operaciones de cambio (5.8) —ténde nota);

Es 1/0 una expresión constante en C11

1 / 0 no es una expresión constante en C11, podemos ver esto en la sección 6.6 Expresiones constantes que dice:

Cada expresión constante se evaluará como una constante que esté en el rango de valores representables para su tipo.

aunque, sí permite:

Una implementación puede aceptar otras formas de expresiones constantes.

Por lo tanto, 1 / 0 no es una expresión constante en C o C ++, pero eso no cambia la respuesta, ya que no se usa en un contexto que requiere una expresión constante. Sospecho que el OP significa que 1 / 0 está disponible para el plegado constante, ya que ambos operandos son literales, esto también explicaría el bloqueo.