style img div attribute c

c - img - title html5



¿Puede el código que nunca se ejecutará invocar un comportamiento indefinido? (9)

Creo que todavía es un comportamiento indefinido, pero no puedo encontrar ninguna evidencia en el estándar para apoyarme o negarme.

Creo que el programa no invoca un comportamiento indefinido.

El Informe de defectos # 109 aborda una pregunta similar y dice:

Además, si cada posible ejecución de un programa determinado daría lugar a un comportamiento indefinido, el programa dado no se ajusta estrictamente. Una implementación conforme no debe dejar de traducir un programa estrictamente conforme simplemente porque una posible ejecución de ese programa daría como resultado un comportamiento indefinido. Debido a que nunca se puede llamar a foo, el ejemplo dado debe traducirse con éxito mediante una implementación conforme.

El código que invoca el comportamiento indefinido (en este ejemplo, división por cero) nunca se ejecutará, ¿el programa aún no está definido?

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 para apoyarme o negarme.

Entonces, ¿alguna idea?


Depende de cómo se define la expresión "comportamiento indefinido" y si el "comportamiento indefinido" de una declaración es lo mismo que "comportamiento indefinido" para un programa.

Este programa se ve como C, por lo que un análisis más profundo de lo que el estándar C utilizado por el compilador (como lo hicieron algunas respuestas) es apropiado.

En ausencia de un estándar especificado, la respuesta correcta es "depende". En algunos idiomas, los compiladores después del primer error intentan adivinar lo que el programador podría significar y aún así generar algún código, de acuerdo con los compiladores. En otros lenguajes más puros, una vez que algo no está definido, la indefinición se propaga a todo el programa.

Otros idiomas tienen un concepto de "errores acotados". Para algunos tipos limitados de errores, estos lenguajes definen cuánto daño puede producir un error. En particular, los idiomas con recolección de basura implícita a menudo hacen la diferencia ya sea que un error invalide el sistema de escritura o no.


El estándar dice que, como recuerdo bien, está permitido hacer cualquier cosa desde el momento en que se rompió una regla. Tal vez hay algunos eventos especiales con un sabor global (pero nunca escuché o leí sobre algo así) ... Así que 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 el tiempo de ejecución.


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):

1 undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements 2 NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

Desde un punto de vista formal, diría que su programa invoca un comportamiento indefinido, lo que significa que la norma no exige 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 comportó como lo espera intuitivamente.


En este caso, el comportamiento indefinido es el resultado de la ejecución del 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 no definido si el comportamiento indefinido fue el resultado únicamente de la declaración del código (por ejemplo, si algún caso de sombreado variable no se definió).


Este article analiza esta pregunta en la sección 2.6:

int main(void){ guard(); 5 / 0; }

Los autores consideran que el programa se define cuando guard() no finaliza. También se encuentran distinguiendo nociones de "estáticamente indefinido" y "dinámicamente indefinido", por ejemplo:

La intención detrás del estándar 11 parece ser que, en general, las situaciones se hacen estáticamente indefinidas si no es fácil generar código para ellas. Solo cuando se puede generar código, la situación puede definirse de forma dinámica.

11) Correspondencia privada con un miembro del comité.

Yo recomendaría mirar el artículo completo. Tomados en conjunto, pinta una imagen consistente.

El hecho de que los autores del artículo tuvieran que debatir la cuestión con un miembro del comité confirma que actualmente el estándar es difuso en cuanto a la respuesta a su pregunta.


Me gustaría ir con el último párrafo de esta respuesta: https://.com/a/18384176/694576

... UB es un problema de tiempo de ejecución, no un problema de compilación ...

Entonces, no, no hay un UB invocado.


Solo cuando el estándar hace cambios de interrupción y su código de repente deja de ser "nunca se ejecuta". Pero no veo ninguna forma lógica en que esto pueda causar un "comportamiento indefinido". No está causando nada .


Veamos cómo el estándar C define los términos "comportamiento" y "comportamiento indefinido".

Las referencias son al borrador N1570 del estándar ISO C 2011; No estoy al tanto de ninguna diferencia relevante en ninguno de los tres estándares ISO C publicados (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 afirmación dada no tiene "apariencia", y ciertamente ninguna "acción", a menos que realmente se ejecute.

Sección 3.4.3:

comportamiento indefinido
comportamiento, al usar una construcción de programa errónea o no portable o 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 comportamiento indefinido varía desde ignorar completamente la situación 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 el 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 sin duda puede tener un comportamiento indefinido, no puede rechazarse en tiempo de compilación.

Como cuestión práctica, los programas tienen que poder realizar pruebas de tiempo de ejecución para protegerse contra la invocación de un comportamiento indefinido, y el estándar tiene que permitir que lo hagan.

Tu ejemplo fue:

if (0) { i = 1/0; }

que nunca ejecuta la división por 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 si y == 0 . El comportamiento está bien definido, y por la misma razón que su ejemplo está bien definido: porque el comportamiento indefinido potencial en realidad nunca puede suceder.

(A menos que INT_MIN < -INT_MAX && x == INT_MIN && y == -1 (sí, la división entera puede desbordarse), pero eso es un problema aparte).

En un comentario (desde que se eliminó), 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 la expresión condicional (que excluye las asignaciones y las 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 casos. Entonces, si escribes:

switch (...) { case 1/0: ... }

entonces 1/0 es una expresión constante, y una que infringe la restricción en 6.6p4: "Cada expresión constante evaluará 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 se evaluara durante la ejecución (o, en el contexto de if (0) , no se evaluó durante la ejecución ().

(Algo que se ve exactamente como una expresión constante no es necesariamente una expresión constante , del mismo modo que, 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 en la sección 6.6 de N1570 que iba a citar:

Por lo 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 se definen para causar un comportamiento indefinido que no se trata de lo que sucede durante la ejecución. El Anexo J, sección 2 del estándar C (de nuevo, vea el N1570 ) enumera las cosas que causan un comportamiento indefinido, recogidas del resto de la norma. Algunos ejemplos (no afirmo que esta es una lista exhaustiva) son:

  • Un archivo fuente no vacío no termina en un carácter de nueva línea que no está precedido inmediatamente 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 fuente básico se encuentra en un archivo fuente, excepto en un identificador, una constante de caracteres, un literal de cadena, un nombre de encabezado, un comentario o un token de preproceso que nunca se convierte en un token
  • Un identificador, comentario, literal de cadena, constante de carácter o nombre de encabezado contiene un carácter multibyte no válido o no comienza y termina en el estado de cambio inicial
  • El mismo identificador tiene enlaces 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 completar.