c++ - ¿Por qué ''d/= d'' no arroja una división por excepción cero cuando d== 0?
compiler-optimization undefined-behavior (3)
C ++ no tiene una excepción "División por cero" para atrapar. El comportamiento que observa es el resultado de las optimizaciones del compilador:
- El compilador asume que el comportamiento indefinido no sucede
- La división por cero en C ++ es un comportamiento indefinido
-
Por lo tanto, se presume que el código que
puede
causar una División por cero no lo hace.
- Y, se supone que el código que debe causar una División por Cero nunca sucederá
-
Por lo tanto, el compilador deduce que debido a que el Comportamiento indefinido no ocurre, entonces las condiciones para el Comportamiento indefinido en este código (
d == 0
) no deben ocurrir -
Por lo tanto,
d / d
siempre debe ser igual a 1.
Sin embargo...
Podemos forzar al compilador a activar una división "real" por cero con un pequeño ajuste en su código.
volatile int d = 0;
d /= d; //What happens?
Así que ahora queda la pregunta: ahora que básicamente hemos forzado al compilador a permitir que esto suceda, ¿qué sucede? Es un comportamiento indefinido, pero ahora hemos evitado que el compilador se optimice en torno a este comportamiento indefinido.
Principalmente, depende del entorno objetivo. Esto no desencadenará una excepción de software, pero puede (dependiendo de la CPU de destino) desencadenar una excepción de hardware (una división de enteros por cero), que no se puede detectar de la manera tradicional que se puede detectar una excepción de software. Este es definitivamente el caso para una CPU x86, y la mayoría de las otras arquitecturas (¡pero no todas!).
Sin embargo, existen métodos para lidiar con la excepción de hardware (si ocurre) en lugar de simplemente dejar que el programa se bloquee: mire esta publicación para ver algunos métodos que podrían ser aplicables: Excepción de captura: dividir por cero . Tenga en cuenta que varían de un compilador a otro.
No entiendo por qué no obtengo una división por excepción cero:
int d = 0;
d /= d;
Esperaba obtener una división por excepción cero pero en su lugar
d == 1
.
¿Por qué
d /= d
arroja una división por excepción cero cuando
d == 0
?
El comportamiento de la división de enteros por cero no está definido por el estándar C ++. No es necesario lanzar una excepción.
(La división de coma flotante por cero también está indefinida, pero IEEE754 lo define).
Su compilador está optimizando
d /= d
a, efectivamente
d = 1
que es una elección razonable.
Se permite hacer esta optimización ya que se puede asumir que no hay un comportamiento indefinido en su código; es decir,
d
no puede ser cero.
Solo para complementar las otras respuestas, el hecho de que la división por cero es un comportamiento indefinido significa que el compilador es libre de hacer cualquier cosa en los casos en que sucedería:
-
El compilador puede suponer que
0 / 0 == 1
y optimizar en consecuencia. Eso es efectivamente lo que parece haber hecho aquí. -
El compilador también podría, si quisiera, asumir que
0 / 0 == 42
y establecerd
en ese valor. -
El compilador también podría decidir que el valor de
d
es indeterminado y, por lo tanto, dejar la variable sin inicializar, de modo que su valor sea lo que se haya escrito previamente en la memoria asignada. Algunos de los valores inesperados observados en otros compiladores en los comentarios pueden ser causados por esos compiladores que hacen algo como esto. - El compilador también puede decidir abortar el programa o generar una excepción cada vez que se produce una división por cero. Dado que, para este programa, el compilador puede determinar que esto siempre sucederá, simplemente puede emitir el código para generar la excepción (o cancelar la ejecución por completo) y tratar el resto de la función como código inalcanzable.
- En lugar de generar una excepción cuando se produce la división por cero, el compilador también podría optar por detener el programa y comenzar un juego de Solitario. Eso también cae bajo el paraguas del "comportamiento indefinido".
- En principio, el compilador podría incluso emitir código que causara que la computadora explotara cada vez que ocurre una división por cero. No hay nada en el estándar de C ++ que lo prohíba. (Para ciertos tipos de aplicaciones, como un controlador de vuelo de misiles, ¡esto incluso podría considerarse una característica de seguridad deseable!)
- Además, el estándar permite explícitamente el comportamiento indefinido para "viajar en el tiempo" , de modo que el compilador también puede hacer cualquiera de las cosas anteriores (o cualquier otra cosa) antes de que ocurra la división por cero. Básicamente, el estándar permite al compilador reordenar libremente las operaciones siempre que no se cambie el comportamiento observable del programa, pero incluso ese último requisito se renuncia explícitamente si la ejecución del programa daría lugar a un comportamiento indefinido. Entonces, en efecto, ¡ todo el comportamiento de cualquier ejecución de programa que, en algún momento, desencadenaría un comportamiento indefinido es indefinido!
-
Como consecuencia de lo anterior, el compilador también puede simplemente
asumir que no ocurre un comportamiento indefinido
, ya que un comportamiento permisible para un programa que se comportaría de manera indefinida en algunas entradas es que simplemente se comporte
como si la entrada hubiera sido algo más
Es decir, incluso si el valor original de
d
no se conocía en el momento de la compilación, el compilador podría suponer que nunca es cero y optimizar el código en consecuencia. En el caso particular del código del OP, esto no se puede distinguir efectivamente del compilador simplemente suponiendo que0 / 0 == 1
, pero el compilador también podría, por ejemplo, asumir que elputs()
enif (d == 0) puts("About to divide by zero!"); d /= d;
if (d == 0) puts("About to divide by zero!"); d /= d;
nunca se ejecuta!