El código que invoca un comportamiento indefinido (en este ejemplo, división por cero) nunca se ejecutará, ¿el programa todavía tiene un comportamiento indefinido?
int main(void)
{
int i;
if(0)
{
i = 1/0;
}
return 0;
}
Creo que todavía es un comportamiento indefinido, pero no puedo encontrar ninguna evidencia en el estándar que me apoye o niegue.
Entonces, ¿alguna idea?
c
language-lawyer
Yu Hao
fuente
fuente
Respuestas:
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:
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:
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:
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; /* set values for x and y */ if (y != 0) { x = x / y; }
La división ciertamente tiene un comportamiento indefinido si
y == 0
, pero nunca se ejecuta siy == 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 deif (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 secuenciax + 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:
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:
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.
fuente
1/0
de ser una expresión constante?1/0
no es una expresión constante en el contexto de la pregunta porque no se analiza como una expresión constante , simplemente como una expresión condicional que forma parte de una expresión de asignación .case 1/0:
violaría la restricción y requeriría un diagnóstico.Este artículo analiza esta cuestión en la sección 2.6:
int main(void){ guard(); 5 / 0; }
Los autores consideran que el programa se define cuando
guard()
no termina. También se encuentran distinguiendo las nociones de "estáticamente indefinido" y "dinámicamente indefinido", por ejemplo:Recomendaría mirar el artículo completo. En conjunto, pinta una imagen coherente.
El hecho de que los autores del artículo tuvieran que discutir la pregunta con un miembro del comité confirma que el estándar actualmente es confuso en la respuesta a su pregunta.
fuente
guard()
no es indefinido), el comportamiento es indefinido si y solo si la declaración5 / 0;
se ejecuta realmente. (Tenga en cuenta que un compilador podría reemplazar legítimamente la evaluación de5 / 0
con una llamada aabort()
o algo similar; el programa entonces abortaría si y solo si la ejecución llega a ese punto). Un compilador puede rechazar ese programa solo si puede determinar queguard()
siempre terminará.guard()
no termina no tiene que generar ningún código para 5/0. Por el contrario, no hay forma de generar código para(int)(void)5
, no se puede simplemente generar el código para(int)(void)z
, ya que eso tampoco es correcto. Entonces los autores piensan que ...if (0) (int)(void)5;
debido al enigma que presenta al compilador ingenuo, mientras que el UB dinámico inalcanzable comoif (0) 5 / 0;
es inofensivo. Esto es lo que trascendió de su discusión con un miembro del comité y he visto un argumento similar en otro lugar (pero quizás de la misma fuente, especialmente porque no recuerdo dónde estaba). Estoy revisando el fundamento del C99 en este momento, si veo alguna mención de esto, volveré y se lo señalaré.(int)(void)5
es una violación de la restricción. N1570 6.5.4, que describe el operador de conversión: "Restricciones: a menos que el nombre del tipo especifique un tipo vacío, el nombre del tipo debe especificar el tipo escalar atómico, calificado o no calificado, y el operando debe tener un tipo escalar".(void)5
no tiene tipo escalar, por lo(int)(void)5
que viola esa restricción, independientemente de si el código que lo contiene se ejecuta alguna vez.En este caso, el comportamiento indefinido es el resultado de ejecutar el código. Entonces, si el código no se ejecuta, no hay un comportamiento indefinido.
El código no ejecutado podría invocar un comportamiento indefinido si el comportamiento indefinido fuera el resultado únicamente de la declaración del código (por ejemplo, si algún caso de sombreado de variables no estaba definido).
fuente
#include "//e"
cuál invoca a UB.Yo iría con el último párrafo de esta respuesta: https://stackoverflow.com/a/18384176/694576
Entonces, no, no se invoca ningún UB.
fuente
Solo cuando el estándar realiza cambios importantes y su código de repente ya no "nunca se ejecuta". Pero no veo ninguna forma lógica en la que esto pueda causar un 'comportamiento indefinido'. No está causando nada .
fuente
En el tema del comportamiento indefinido, a menudo es difícil separar los aspectos formales de los prácticos. Esta es la definición de comportamiento indefinido en el estándar de 1989 (no tengo una versión más reciente a la mano, pero no espero que esto haya cambiado sustancialmente):
Desde un punto de vista formal, diría que su programa invoca un comportamiento indefinido, lo que significa que el estándar no impone ningún requisito sobre lo que hará cuando se ejecute, solo porque contiene división por cero.
Por otro lado, desde un punto de vista práctico, me sorprendería encontrar un compilador que no se comportara como esperas intuitivamente.
fuente
El estándar dice, como recuerdo bien, está permitido hacer cualquier cosa desde el momento en que se rompió una regla. Tal vez haya algunos eventos especiales con una especie de sabor global (pero nunca escuché o leí sobre algo así) ... Entonces yo diría: No, esto no puede ser UB, porque mientras el comportamiento esté bien definido, 0 es siempre falso, por lo que la regla no se puede romper en tiempo de ejecución.
fuente
Creo que el programa no invoca un comportamiento indefinido.
El Informe de defectos n. ° 109 aborda una pregunta similar y dice:
fuente
Depende de cómo se defina la expresión "comportamiento indefinido" y de si "comportamiento indefinido" de una declaración es lo mismo que "comportamiento indefinido" para un programa.
Este programa se parece a C, por lo que es apropiado un análisis más profundo de cuál es el estándar C utilizado por el compilador (como lo hicieron algunas respuestas).
En ausencia de un estándar específico, la respuesta correcta es "depende". En algunos lenguajes, los compiladores después del primer error intentan adivinar lo que podría significar el programador y aún así generan algo de código, según la conjetura de los compiladores. En otros lenguajes más puros, una vez que algo está indefinido, la indefinición se propaga a todo el programa.
Otros lenguajes tienen un concepto de "errores limitados". Para algunos tipos limitados de errores, estos lenguajes definen cuánto daño puede producir un error. En particular, los lenguajes con recolección de basura implícita con frecuencia marcan la diferencia si un error invalida el sistema de mecanografía o no.
fuente