Comportamiento indefinido, no especificado y definido por la implementación

¿Cuál es la diferencia entre el comportamiento indefinido, no especificado y definido por la implementación en C y C ++?

El comportamiento indefinido es uno de esos aspectos del lenguaje C y C ++ que puede sorprender a los progtwigdores que provienen de otros idiomas (otros idiomas intentan ocultarlo mejor). Básicamente, es posible escribir progtwigs C ++ que no se comportan de manera predecible, ¡aunque muchos comstackdores C ++ no informarán ningún error en el progtwig!

Veamos un ejemplo clásico:

#include  int main() { char* p = "hello!\n"; // yes I know, deprecated conversion p[0] = 'y'; p[5] = 'w'; std::cout < < p; } 

La variable p apunta al literal de cadena "hello!\n" , y las dos asignaciones siguientes intentan modificar ese literal de cadena. ¿Qué hace este progtwig? De acuerdo con el párrafo 11 de la sección 2.14.5 del estándar C ++, invoca un comportamiento indefinido :

El efecto de intentar modificar un literal de cadena no está definido.

Puedo escuchar a la gente gritando "Pero espera, puedo comstackr esto sin problemas y obtener la salida yellow " o "¿Qué quieres decir indefinido? Los literales de cadena se almacenan en memoria de solo lectura, por lo que el primer bash de asignación da como resultado un volcado del núcleo" . Este es exactamente el problema con el comportamiento indefinido. Básicamente, el estándar permite que algo suceda una vez que invocas un comportamiento indefinido (incluso demonios nasales). Si hay un comportamiento "correcto" de acuerdo con su modelo mental del idioma, ese modelo es simplemente incorrecto; El estándar de C ++ tiene el único voto, punto.

Otros ejemplos de comportamiento indefinido incluyen el acceso a una matriz más allá de sus límites, la desreferenciación del puntero nulo , el acceso a los objetos una vez finalizado su ciclo de vida o la escritura de expresiones supuestamente ingeniosas como i++ + ++i .

La sección 1.9 del estándar C ++ también menciona dos comportamientos indefinidos de hermanos menos peligrosos, comportamiento no especificado y comportamiento definido por la implementación :

Las descripciones semánticas en este estándar internacional definen una máquina abstracta no determinista parametrizada.

Ciertos aspectos y operaciones de la máquina abstracta se describen en esta norma internacional como definidos por la implementación (por ejemplo, sizeof(int) ). Estos constituyen los parámetros de la máquina abstracta. Cada implementación debe incluir documentación que describa sus características y comportamiento en estos aspectos.

Algunos otros aspectos y operaciones de la máquina abstracta se describen en esta norma internacional como no especificados (por ejemplo, orden de evaluación de argumentos para una función). Donde sea posible, este Estándar Internacional define un conjunto de comportamientos permitidos. Estos definen los aspectos no deterministas de la máquina abstracta.

Algunas otras operaciones se describen en esta norma internacional como no definidas (por ejemplo, el efecto de desreferenciar el puntero nulo). [ Nota : esta Norma Internacional no impone requisitos sobre el comportamiento de los progtwigs que contienen un comportamiento indefinido. - nota final ]

Específicamente, la sección 1.3.24 establece:

El comportamiento indefinido permitido va desde ignorar la situación completamente con resultados impredecibles , hasta comportarse durante la traducción o la ejecución del progtwig 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).

¿Qué puedes hacer para evitar toparse con un comportamiento indefinido? Básicamente, tienes que leer buenos libros en C ++ escritos por autores que saben de lo que están hablando. Atornille los tutoriales de internet. Atornille bullschildt.

Bueno, esto es básicamente un copy-paste directo del estándar

3.4.1 1 Comportamiento no especificado de comportamiento definido por implementación donde cada implementación documenta cómo se realiza la elección

2 EJEMPLO Un ejemplo de comportamiento definido por la implementación es la propagación del bit de orden superior cuando un entero con signo se desplaza hacia la derecha.

3.4.3 1 comportamiento de comportamiento indefinido , al usar una construcción de progtwig errónea o no portable o datos erróneos, para los cuales esta Norma Internacional no impone requisitos

2 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 progtwig 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).

3 EJEMPLO Un ejemplo de comportamiento indefinido es el comportamiento en el desbordamiento de enteros.

3.4.4 1 uso de comportamiento no especificado de un valor no especificado u otro comportamiento donde esta Norma Internacional proporciona dos o más posibilidades y no impone requisitos adicionales sobre los cuales se elige en cualquier caso

2 EJEMPLO Un ejemplo de comportamiento no especificado es el orden en que se evalúan los argumentos para una función.

Tal vez la redacción fácil podría ser más fácil de entender que la definición rigurosa de los estándares.

comportamiento definido por la implementación
El lenguaje dice que tenemos tipos de datos. Los proveedores del comstackdor especifican qué tamaños deben usar y proporcionan una documentación de lo que hicieron.

comportamiento indefinido
Estas haciendo algo mal. Por ejemplo, tiene un valor muy grande en un int que no cabe en char . ¿Cómo pones ese valor en char ? en realidad no hay forma! Cualquier cosa podría pasar, pero lo más sensato sería tomar el primer byte de ese int y ponerlo en char . Es simplemente incorrecto hacer eso para asignar el primer byte, pero eso es lo que sucede debajo del capó.

comportamiento no especificado
¿Qué función de estos dos se ejecuta primero?

 void fun(int n, int m); int fun1() { cout < < "fun1"; return 1; } int fun2() { cout << "fun2"; return 2; } ... fun(fun1(), fun2()); // which one is executed first? 

¡El idioma no especifica la evaluación, de izquierda a derecha o de derecha a izquierda! Entonces, un comportamiento no especificado puede o no dar como resultado un comportamiento indefinido, pero ciertamente su progtwig no debe producir un comportamiento no especificado.


@eSKay Creo que su pregunta vale la pena editar la respuesta para aclarar más 🙂

por fun(fun1(), fun2()); ¿no es el comportamiento "implementación definida"? El comstackdor tiene que elegir uno u otro curso, después de todo?

La diferencia entre la implementación definida y no especificada es que el comstackdor debe elegir un comportamiento en el primer caso, pero no tiene que hacerlo en el segundo caso. Por ejemplo, una implementación debe tener una única definición de sizeof(int) . Por lo tanto, no puede decir que sizeof(int) es 4 para una parte del progtwig y 8 para otros. A diferencia del comportamiento no especificado, donde el comstackdor puede decir OK, voy a evaluar estos argumentos de izquierda a derecha y los argumentos de la siguiente función se evalúan de derecha a izquierda. Puede suceder en el mismo progtwig, es por eso que se llama no especificado . De hecho, C ++ podría haberse hecho más fácil si se especificaran algunos de los comportamientos no especificados. Eche un vistazo aquí a la respuesta del Dr. Stroustrup para eso :

Se afirma que la diferencia entre lo que se puede producir y dar al comstackdor esta libertad y que requiere una "evaluación común de izquierda a derecha" puede ser significativa. No estoy convencido, pero con innumerables comstackdores "allá afuera" que aprovechan la libertad y algunas personas que defienden apasionadamente esa libertad, un cambio sería difícil y podría llevar décadas penetrar en los rincones distantes de los mundos C y C ++. Estoy decepcionado de que no todos los comstackdores adviertan contra el código como ++ i + i ++. Del mismo modo, el orden de evaluación de los argumentos no está especificado.

OMI demasiadas "cosas" se dejan indefinidas, no especificadas, definidas por la implementación, etc. Sin embargo, eso es fácil de decir e incluso dar ejemplos, pero es difícil de arreglar. También se debe tener en cuenta que no es tan difícil evitar la mayoría de los problemas y producir código portátil.

Del documento oficial C Rationale

Los términos comportamiento no especificado , comportamiento indefinido y comportamiento definido por la implementación se utilizan para categorizar el resultado de escribir progtwigs cuyas propiedades el Estándar no describe, o no puede, describir por completo. El objective de adoptar esta categorización es permitir una cierta variedad entre implementaciones que permita que la calidad de implementación sea una fuerza activa en el mercado, así como también permitir ciertas extensiones populares, sin eliminar el prestigio de conformidad con el Estándar. El Apéndice F del Estándar cataloga aquellos comportamientos que caen en una de estas tres categorías.

El comportamiento no especificado le da al implementador cierta libertad para traducir progtwigs. Esta latitud no se extiende hasta el punto de no poder traducir el progtwig.

El comportamiento indefinido le otorga al implementador una licencia para no detectar ciertos errores del progtwig que son difíciles de diagnosticar. También identifica áreas de posible extensión del lenguaje conforme: el implementador puede boost el idioma al proporcionar una definición del comportamiento oficialmente indefinido.

El comportamiento definido por la implementación brinda al implementador la libertad de elegir el enfoque apropiado, pero requiere que esta opción se explique al usuario. Los comportamientos designados como definidos por la implementación son generalmente aquellos en los que un usuario puede tomar decisiones de encoding significativas basadas en la definición de la implementación. Los implementadores deben tener en cuenta este criterio al decidir qué tan extensa debe ser la definición de implementación. Al igual que con el comportamiento no especificado, el simple hecho de no traducir la fuente que contiene el comportamiento definido por la implementación no es una respuesta adecuada.

Comportamiento indefinido vs. Comportamiento no especificado tiene una breve descripción del mismo.

Su resumen final:

En resumen, el comportamiento no especificado generalmente es algo de lo que no debe preocuparse, a menos que se requiera que su software sea portátil. Por el contrario, el comportamiento indefinido siempre es indeseable y nunca debería ocurrir.

Históricamente, tanto el Comportamiento definido por la implementación como el Comportamiento indefinido representaban situaciones en las que los autores esperaban que las personas que escriben las implementaciones de calidad usaran el juicio para decidir qué garantías de comportamiento serían útiles para los progtwigs en el campo de aplicación previsto que se ejecuta en el objectives previstos Las necesidades del código de procesamiento de números de gama alta son bastante diferentes de las del código de sistemas de bajo nivel, y tanto UB como IDB le dan flexibilidad a los escritores de comstackdores para satisfacer esas necesidades diferentes. Ninguna categoría exige que las implementaciones se comporten de una manera que sea útil para un propósito particular, o incluso para cualquier propósito. Sin embargo, las implementaciones de calidad que pretendan ser adecuadas para un propósito particular, deben comportarse de una manera acorde con dicho propósito, ya sea que el Estándar lo requiera o no .

La única diferencia entre el Comportamiento definido por la implementación y el Comportamiento indefinido es que el primero requiere que las implementaciones definan y documenten un comportamiento uniforme incluso en los casos en los que la implementación no podría hacer nada útil . La línea divisoria entre ellos no es si generalmente sería útil para las implementaciones definir comportamientos (los escritores de comstackdores deberían definir comportamientos útiles cuando sea práctico si el Estándar así lo requiere o no), sino si podría haber implementaciones donde definir un comportamiento sería simultáneamente costoso. e inútil El juicio de que tales implementaciones pueden existir no implica, de ninguna manera, forma o forma, ningún juicio sobre la utilidad de soportar un comportamiento definido en otras plataformas.

Desafortunadamente, desde mediados de la década de 1990 los escritores de comstackdores han comenzado a interpretar la falta de mandatos de comportamiento como un juicio de que las garantías de comportamiento no valen la pena, incluso en campos de aplicación donde son vitales, e incluso en sistemas donde no cuestan prácticamente nada. En lugar de tratar a UB como una invitación a ejercer un juicio razonable, los escritores de comstackdores han comenzado a tratarlo como una excusa para no hacerlo.

Por ejemplo, dado el siguiente código:

 int scaled_velocity(int v, unsigned char pow) { if (v > 250) v = 250; if (v < -250) v = -250; return v << pow; } 

una implementación de dos complementos no tendría que gastar ningún esfuerzo para tratar la expresión v < < pow como un cambio de complemento a dos sin tener en cuenta si v era positivo o negativo.

La filosofía preferida entre algunos de los autores de comstackdores de hoy en día, sin embargo, sugeriría que debido a que v solo puede ser negativo si el progtwig va a involucrarse en un Comportamiento Indefinido, no hay razón para que el progtwig recorte el rango negativo de v . Aunque el desplazamiento a la izquierda de valores negativos solía ser compatible con cada comstackdor de importancia, y una gran cantidad de código existente se basa en ese comportamiento, la filosofía moderna interpretaría el hecho de que el Estándar dice que los valores negativos de desplazamiento a la izquierda son UB como lo que implica que los escritores de comstackdores deberían sentirse libres de ignorar eso.

Implementación definida-

Los implementadores desean, deben estar bien documentados, el estándar da opciones pero seguro comstackr

No especificado –

Igual que la definición de implementación pero no documentada

Indefinido-

Cualquier cosa podría pasar, cuídala.

Norma C ++ n3337 § 1.3.10 comportamiento definido por la implementación

comportamiento, para una construcción de progtwig bien formada y datos correctos, que depende de la implementación y que cada documento de implementación

A veces, C ++ Standard no impone un comportamiento particular en algunos constructos, sino que, en cambio, un comportamiento particular bien definido debe ser elegido y descrito por una implementación particular (versión de la biblioteca). De modo que el usuario todavía puede saber exactamente cómo se comportará el progtwig aunque Standard no describa esto.


Norma C ++ n3337 § 1.3.24 comportamiento indefinido

comportamiento para el cual esta Norma Internacional no impone requisitos [Nota: se puede esperar un comportamiento no definido cuando esta Norma Internacional omite cualquier definición explícita de comportamiento o cuando un progtwig utiliza una construcción errónea o datos erróneos. El comportamiento indefinido permitido va desde ignorar la situación completamente con resultados impredecibles, hasta comportarse durante la traducción o la ejecución del progtwig 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). Muchos constructos erróneos del progtwig no engendran un comportamiento indefinido; se requiere que sean diagnosticados. – nota final]

Cuando el progtwig encuentra un constructo que no está definido de acuerdo con C ++ Standard, se le permite hacer lo que quiera hacer (tal vez enviarme un correo electrónico o quizás enviarle un correo electrónico o quizás ignorar el código por completo).


Norma C ++ n3337 § 1.3.25 comportamiento no especificado

comportamiento, para una construcción de progtwig bien formada y datos correctos, que depende de la implementación [Nota: la implementación no es necesaria para documentar qué comportamiento ocurre. El rango de posibles comportamientos generalmente está delineado por este Estándar Internacional. – nota final]

C ++ Standard no impone un comportamiento particular en algunos constructos, sino que, en cambio, debe seleccionarse un comportamiento particular bien definido ( no se describe el bot ) mediante una implementación particular (versión de la biblioteca). Entonces, en el caso de que no se haya proporcionado una descripción, puede ser difícil para el usuario saber exactamente cómo se comportará el progtwig.

Hay muchos constructos que deberían comportarse de manera útil y predecible en algunos casos, pero prácticamente no se puede hacer en todos los casos en todas las implementaciones. A menudo, el conjunto de casos para los cuales una construcción debería ser utilizable dependerá de la plataforma objective y el campo de aplicación. Debido a que la implementación para diferentes objectives y campos debe manejar diferentes conjuntos de casos, el Estándar considera la cuestión de qué casos tratar como un problema de Calidad de Implementación. Además, debido a que los autores de la Norma no vieron la necesidad de prohibir las implementaciones “conformes” por ser de una calidad tan pobre que no sirven para nada, a menudo no se molestan en ordenar explícitamente el comportamiento de los casos que esperaban que todas las implementaciones no basura incluso sin un mandato.

Por ejemplo, el código:

 struct foo {int x;} = {0}; int main(void) { foo.x = 1; return foo.x-1; } 

utiliza un lvalue de tipo int [es decir, foo.x ] para acceder al valor almacenado de un objeto de tipo struct foo , aunque N1570 6.5p7 no contiene nada que permita acceder a un objeto de tipo struct foo excepto a través de un lvalue de tipo struct foo o un lvalue de un tipo de carácter, y el Standard no contiene ningún lenguaje que exima las expresiones struct-member-access de los requisitos de 6.5p7.

Obviamente, cualquier comstackdor que no pueda manejar expresiones simples de acceso a miembros de struct debe considerarse de una calidad excepcionalmente baja y probablemente no adecuado para gran parte de cualquier cosa. En consecuencia, debería ser razonable esperar que cualquier persona que busque producir una implementación de calidad respalde dicha construcción independientemente de si la Norma lo ordena o no. Siempre que se pueda confiar en los escritores de comstackdores, hacer un esfuerzo genuino para producir comstackdores de calidad que sean adecuados para los fines previstos, y estar abiertos sobre los propósitos para los cuales sus comstackdores son o no adecuados, no habría razón para tener la tinta de desecho estándar. tratando de decir cosas que deberían ser obvias. Muchas acciones que deberían tener comportamientos utilizables y predecibles son, de hecho, Comportamiento Indefinido porque los autores del Estándar confiaban en que los comstackdores jugaran un juicio razonable, en lugar de utilizar el hecho de que las acciones invocan el Comportamiento Indefinido como una excusa para echar un vistazo a la ventana .