Veamos cómo el estándar C define los términos "comportamiento" y "comportamiento indefinido".
Las referencias son al borrador N1570 de la norma ISO C 2011; No tengo conocimiento de ninguna diferencia relevante en ninguna de las tres normas ISO C publicadas (1990, 1999 y 2011).
Sección 3.4:
comportamiento
apariencia o acción externa
Ok, eso es un poco vago, pero yo diría que una declaración dada no tiene "apariencia", y ciertamente no tiene "acción", a menos que realmente se ejecute.
Sección 3.4.3:
Comportamiento indefinido comportamiento
, ante 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.
Dice " sobre el uso " de tal construcción. La palabra "uso" no está definida por el estándar, por lo que recurrimos al significado común en inglés. Una construcción no se "usa" si nunca se ejecuta.
Hay una nota bajo esa definición:
NOTA El posible comportamiento indefinido va desde ignorar la situación por completo con resultados impredecibles, hasta comportarse durante la traducción o 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).
Por lo tanto, un compilador puede rechazar su programa en tiempo de compilación si su comportamiento no está definido. Pero mi interpretación de eso es que puede hacerlo solo si puede probar que cada ejecución del programa encontrará un comportamiento indefinido. Lo que implica, creo, que esto:
if (rand() % 2 == 0) {
i = i / 0;
}
que ciertamente puede tener un comportamiento indefinido, no se puede rechazar en tiempo de compilación.
En la práctica, los programas deben poder realizar pruebas en tiempo de ejecución para evitar invocar un comportamiento indefinido, y el estándar debe permitirles hacerlo.
Tu ejemplo fue:
if (0) {
i = 1/0;
}
que nunca ejecuta la división entre 0. Un modismo muy común es:
int x, y;
if (y != 0) {
x = x / y;
}
La división ciertamente tiene un comportamiento indefinido si y == 0
, pero nunca se ejecuta si y == 0
. El comportamiento está bien definido y por la misma razón que su ejemplo está bien definido: porque el comportamiento potencial indefinido nunca puede suceder realmente.
(A menos que INT_MIN < -INT_MAX && x == INT_MIN && y == -1
(sí, la división de enteros puede desbordarse), pero ese es un problema aparte).
En un comentario (ya eliminado), alguien señaló que el compilador puede evaluar expresiones constantes en tiempo de compilación. Lo cual es cierto, pero no relevante en este caso, porque en el contexto de
i = 1/0;
1/0
no es una expresión constante .
Una expresión-constante es una categoría sintáctica que se reduce a expresión-condicional (que excluye asignaciones y expresiones de coma). La expresión-constante de producción aparece en la gramática solo en contextos que realmente requieren una expresión constante, como las etiquetas de caso. Entonces, si escribe:
switch (...) {
case 1/0:
...
}
entonces 1/0
es una expresión constante - y una que viola la restricción en 6.6p4: "Cada expresión constante se evaluará a una constante que está en el rango de valores representables para su tipo", por lo que se requiere un diagnóstico. Pero el lado derecho de una asignación no requiere una expresión-constante , simplemente una expresión-condicional , por lo que las restricciones en las expresiones constantes no se aplican. Un compilador puede evaluar cualquier expresión que pueda en tiempo de compilación, pero solo si el comportamiento es el mismo que si fuera evaluado durante la ejecución (o, en el contexto de if (0)
, no evaluado durante la ejecución ().
(Algo que se ve exactamente como una expresión-constante no es necesariamente una expresión-constante , así como, en x + y * z
, la secuencia x + y
no es una expresión-aditiva debido al contexto en el que aparece).
Lo que significa la nota al pie de la sección 6.6 de N1570 que iba a citar:
Por tanto, en la siguiente inicialización,
static int i = 2 || 1 / 0;
la expresión es una expresión constante entera válida con valor uno.
no es realmente relevante para esta pregunta.
Finalmente, hay algunas cosas que están definidas para causar un comportamiento indefinido que no se trata de lo que sucede durante la ejecución. El anexo J, sección 2 de la norma C (nuevamente, consulte el borrador N1570 ) enumera las cosas que causan un comportamiento indefinido, recopiladas del resto de la norma. Algunos ejemplos (no afirmo que esta sea una lista exhaustiva) son:
- Un archivo de origen no vacío no termina en un carácter de nueva línea que no está inmediatamente precedido por un carácter de barra invertida o termina en un token o comentario de preprocesamiento parcial
- La concatenación de tokens produce una secuencia de caracteres que coincide con la sintaxis de un nombre de carácter universal
- Un carácter que no está en el juego de caracteres de origen básico se encuentra en un archivo de origen, excepto en un identificador, una constante de carácter, un literal de cadena, un nombre de encabezado, un comentario o un token de preprocesamiento que nunca se convierte en un token
- Un identificador, comentario, literal de cadena, constante de caracteres o nombre de encabezado contiene un carácter multibyte no válido o no comienza ni termina en el estado de cambio inicial
- El mismo identificador tiene vínculos internos y externos en la misma unidad de traducción
Estos casos particulares son cosas que un compilador podría detectar. Creo que su comportamiento no está definido porque el comité no quería, o no podía, imponer el mismo comportamiento en todas las implementaciones, y definir un rango de comportamientos permitidos simplemente no valía la pena. Realmente no entran en la categoría de "código que nunca se ejecutará", pero los menciono aquí para completarlos.