La mayoría de los tipos de UB de los que normalmente nos preocupamos, como NULL-deref o dividir por cero, son UB en tiempo de ejecución . Compilar una función que causaría UB en tiempo de ejecución si se ejecuta no debe causar que el compilador se bloquee. A menos que tal vez pueda probar que la función (y esa ruta a través de la función) definitivamente será ejecutada por el programa.
(Segundo pensamiento: tal vez no he considerado la evaluación requerida de template / constexpr en el momento de la compilación. Posiblemente UB durante eso puede causar rarezas arbitrarias durante la traducción, incluso si nunca se llama a la función resultante).
La parte de comportamiento durante la traducción de la cita ISO C ++ en la respuesta de @ StoryTeller es similar al lenguaje utilizado en el estándar ISO C. C no incluye plantillas ni constexpr
evaluaciones obligatorias en tiempo de compilación.
Pero dato curioso: ISO C dice en una nota que si se termina la traducción, debe ser con un mensaje de diagnóstico. O "comportarse durante la traducción ... de manera documentada". No creo que "ignorar la situación por completo" pueda interpretarse como una interrupción de la traducción.
Respuesta anterior, escrita antes de que aprendiera sobre la UB en tiempo de traducción. Sin embargo, es cierto para runtime-UB y, por lo tanto, todavía es potencialmente útil.
No existe tal cosa como UB que suceda en tiempo de compilación. Puede ser visible para el compilador a lo largo de una determinada ruta de ejecución, pero en términos de C ++ no ha sucedido hasta que la ejecución alcanza esa ruta de ejecución a través de una función.
Los defectos en un programa que hacen imposible la compilación no son UB, son errores de sintaxis. Dicho programa "no está bien formado" en terminología C ++ (si tengo mi standardese correcto). Un programa puede estar bien formado pero contener UB. Diferencia entre comportamiento indefinido y mal formado, no se requiere mensaje de diagnóstico
A menos que no entienda algo, ISO C ++ requiere que este programa se compile y se ejecute correctamente, porque la ejecución nunca llega a la división por cero. (En la práctica ( Godbolt ), los buenos compiladores solo crean ejecutables que funcionan. Gcc / clang advierte x / 0
pero no esto, incluso cuando se optimiza. Pero de todos modos, estamos tratando de decir qué tan bajo ISO C ++ permite que sea la calidad de implementación. Así que verificando gcc / clang no es una prueba útil más que para confirmar que escribí el programa correctamente).
int cause_UB() {
int x=0;
return 1 / x;
}
int main(){
if (0)
cause_UB();
}
Un caso de uso para esto podría involucrar el preprocesador de C, o las constexpr
variables y la ramificación en esas variables, lo que conduce a una tontería en algunas rutas que nunca se alcanzan para esas elecciones de constantes.
Se puede suponer que las rutas de ejecución que causan UB visibles en tiempo de compilación nunca se toman, por ejemplo, un compilador para x86 podría emitir una ud2
(causar una excepción de instrucción ilegal) como la definición de cause_UB()
. O dentro de una función, si un lado de un if()
conduce a un UB demostrable , la rama se puede quitar.
Pero el compilador todavía tiene que compilar todo lo demás de una manera sana y correcta. Todas las rutas que no encuentran (o no se puede probar que encuentren) UB aún deben compilarse en un asm que se ejecuta como si la máquina abstracta de C ++ lo estuviera ejecutando.
Se podría argumentar que la UB incondicional visible en tiempo de compilación en main
es una excepción a esta regla. O de lo contrario, comprobable en tiempo de compilación que la ejecución que comienza en main
, de hecho, alcanza UB garantizado.
Todavía diría que los comportamientos legales del compilador incluyen producir una granada que explota si se ejecuta. O más plausiblemente, una definición de main
eso consiste en una sola instrucción ilegal. Yo diría que si nunca ejecuta el programa, todavía no ha habido ningún UB. El compilador en sí no puede explotar, en mi opinión.
Funciones que contienen ramas internas de UB posibles o demostrables
UB a lo largo de cualquier ruta de ejecución determinada se remonta en el tiempo para "contaminar" todo el código anterior. Pero en la práctica, los compiladores solo pueden aprovechar esa regla cuando realmente pueden demostrar que las rutas de ejecución conducen a UB visibles en tiempo de compilación. p.ej
int minefield(int x) {
if (x == 3) {
*(char*)nullptr = x/0;
}
return x * 5;
}
El compilador tiene que hacer un asm que funcione para todos los x
demás que no sean 3, hasta los puntos donde x * 5
causa UB de desbordamiento firmado en INT_MIN e INT_MAX. Si esta función nunca se llama con x==3
, el programa, por supuesto, no contiene UB y debe funcionar como está escrito.
Bien podríamos haber escrito if(x == 3) __builtin_unreachable();
en GNU C para decirle al compilador que x
definitivamente no es 3.
En la práctica, hay un código de "campo minado" por todas partes en los programas normales. por ejemplo, cualquier división por un número entero promete al compilador que no es cero. Cualquier puntero deref promete al compilador que no es NULL.