c++ c undefined-behavior c++-faq unspecified-behavior

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



undefined-behavior c++-faq (9)

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


Bueno, esto es básicamente una copia y pegar directamente desde el estándar

3.4.1 1 comportamiento definido por la implementación comportamiento no especificado 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 , en 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

2 NOTA El posible comportamiento indefinido abarca desde ignorar la situación completamente 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 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 comportamiento no especificado: uso 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 que se elija en ningún caso

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


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

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

A veces, C ++ Standard no impone un comportamiento particular en algunas construcciones, sino que dice que un comportamiento particular y bien definido debe ser elegido y descrito por una implementación particular (versión de la biblioteca). De modo que el usuario aún puede saber exactamente cómo se comportará el programa a pesar de que Standard no lo describe.

C ++ standard n3337 § 1.3.24 comportamiento indefinido

comportamiento para el cual esta Norma Internacional no impone requisitos [Nota: se puede esperar un comportamiento indefinido cuando esta Norma Internacional omite cualquier definición explícita de comportamiento o cuando un programa utiliza un constructo erróneo o datos erróneos. El comportamiento indefinido permisible va desde ignorar la situación completamente con resultados impredecibles, a 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 la emisión de un mensaje de diagnóstico). Muchas construcciones de programas erróneas no generan un comportamiento indefinido; se requieren para ser diagnosticados. - nota final]

Cuando el programa encuentra una construcción que no está definida de acuerdo con el estándar de C ++, se le permite hacer lo que quiera hacer (tal vez me envíe un correo electrónico o tal vez le envíe un correo electrónico o tal vez ignore el código por completo).

C ++ estándar n3337 § 1.3.25 comportamiento no especificado

comportamiento, para una construcción de programa 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 comportamientos posibles generalmente está delineado por esta Norma Internacional. - nota final]

C ++ Standard no impone un comportamiento particular en algunas construcciones, sino que dice que una implementación particular (versión de biblioteca) debe elegir un comportamiento particular bien definido ( bot no es necesario describirlo ). Por lo tanto, 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 programa.


Del documento oficial de justificación C

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

El comportamiento no especificado le da al implementador cierta libertad en la traducción de programas. Esta latitud no se extiende hasta el punto de no traducir el programa.

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

El comportamiento definido por la implementación le da al implementador la libertad de elegir el enfoque apropiado, pero requiere que esta elección sea explicada al usuario. Los comportamientos designados como definidos por la implementación son generalmente aquellos en los que un usuario podría tomar decisiones de codificación 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 una definición de implementación. Al igual que con el comportamiento no especificado, simplemente no traducir la fuente que contiene el comportamiento definido por la implementación no es una respuesta adecuada.


Hay muchos constructos que deberían comportarse de una manera útil y predecible en algunos casos, pero prácticamente no se puede hacer para hacerlo en todos los casos en todas las implementaciones. A menudo, el conjunto de casos para los cuales una construcción debe ser utilizable dependerá de la plataforma de destino y el campo de aplicación. Debido a que la implementación para diferentes objetivos y campos debe manejar diferentes conjuntos de casos, la Norma considera la cuestión de qué casos tratar como un problema de Calidad de la implementación. Además, debido a que los autores de la Norma no vieron la necesidad de prohibir que las implementaciones "conformes" fueran de una calidad tan deficiente como para ser inútiles, a menudo no se molestan en exigir explícitamente el comportamiento de los casos que esperaban que todas las implementaciones que no eran basura apoyen 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, ni el Estándar contiene ningún lenguaje que exima a las expresiones de acceso de struct-member de los requisitos de 6.5p7.

Obviamente, cualquier compilador que no pueda manejar expresiones de acceso a estructuras simples debe considerarse de excepcional calidad baja y probablemente no sea adecuado para nada. En consecuencia, debe ser razonable esperar que cualquiera que busque producir una implementación de calidad apoyará tal construcción independientemente de si la Norma lo exige o no. Siempre que se pueda confiar en los escritores de compiladores, realizar un esfuerzo fidedigno para producir compiladores de calidad que sean adecuados para sus propósitos previstos, y ser abiertos sobre los propósitos para los cuales sus compiladores son adecuados o no, no habría razón para tener la tinta de desecho estándar. Tratando de establecer cosas que deberían ser obvias. Muchas acciones que deberían tener comportamientos utilizables y predecibles son, de hecho, comportamiento indefinido porque los autores de los escritores de compiladores de confianza estándar ejercen un juicio razonable, en lugar de usar el hecho de que las acciones invocan el comportamiento indefinido como una excusa para echar el juicio por la ventana .


Históricamente, tanto el Comportamiento Definido por la Implementación como el Comportamiento No Definido representaban situaciones en las que los autores del Estándar esperaban que las personas que escribieran implementaciones de calidad usarían su juicio para decidir qué garantías de comportamiento, si las hubiera, serían útiles para los programas en el campo de aplicación previsto que se ejecuta en el campo objetivos previstos. Las necesidades del código de procesamiento de números de gama alta son muy diferentes de las del código de sistemas de bajo nivel, y tanto UB como el BID dan a los escritores de compilación flexibilidad para satisfacer esas diferentes necesidades. Ninguna de las categorías exige que las implementaciones se comporten de una manera que sea útil para un propósito en particular, o incluso para cualquier propósito en absoluto. Sin embargo, las implementaciones de calidad que pretenden ser adecuadas para un propósito en particular, deben comportarse de una manera acorde con tal propósito, ya sea que la Norma 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 coherente, incluso en los casos en que nada de lo que podría hacer la implementación sería útil . La línea divisoria entre ellos no es si sería generalmente útil para que las implementaciones definan comportamientos (los escritores del compilador deberían definir comportamientos útiles cuando sea práctico si el Estándar los requiere o no), sino si puede haber implementaciones donde definir un comportamiento sería simultáneamente costoso e inútil . Un juicio de que tales implementaciones puedan existir no implica, de ninguna manera, forma o forma, implica 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 compiladores han empezado a interpretar la falta de mandatos de comportamiento como un juicio de que las garantías de comportamiento no valen la pena ni siquiera en los 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 compiladores han comenzado a tratarla 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 complemento a dos no tendría que realizar ningún esfuerzo para tratar la expresión v << pow como un cambio de complemento a dos sin tener en cuenta si v era positiva o negativa.

Sin embargo, la filosofía preferida entre algunos de los compiladores de compiladores actuales sugiere que, dado que v solo puede ser negativo si el programa se involucra en un comportamiento indefinido, no hay razón para que el programa recorte el rango negativo de v . Aunque el desplazamiento a la izquierda de los valores negativos solía ser compatible con cada compilador 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 del desplazamiento a la izquierda son UB como lo que implica que los escritores de compiladores deberían sentirse libres de ignorar eso.


Implementación definida

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

Sin especificar

Igual que la implementación definida pero no documentada

Indefinido-

Cualquier cosa puede pasar, cuídalo.


Tal vez una 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 compilador 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 se pone ese valor en char ? en realidad no hay manera! Cualquier cosa podría suceder, pero lo más sensato sería tomar el primer byte de ese int y ponerlo en la char . Es 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! Por lo tanto, un comportamiento no especificado puede o no resultar en un comportamiento indefinido, pero ciertamente su programa no debe producir un comportamiento no especificado.

@eSKay Creo que vale la pena editar la respuesta para aclarar más :)

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

La diferencia entre la implementación definida y la no especificada, es que se supone que el compilador elige un comportamiento en el primer caso, pero no tiene que hacerlo en el segundo caso. Por ejemplo, una implementación debe tener una y solo una definición de sizeof(int) . Por lo tanto, no puedo decir que sizeof(int) sea ​​4 para una parte del programa y 8 para otras. A diferencia del comportamiento no especificado, donde el compilador 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 programa, por eso se llama no especificado . De hecho, C ++ podría haberse simplificado si se hubieran especificado 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 puede producirse dando al compilador esta libertad y exigir una "evaluación ordinaria de izquierda a derecha" puede ser significativa. No estoy convencido, pero con innumerables compiladores "allá afuera" aprovechando la libertad y algunas personas que defienden apasionadamente esa libertad, un cambio sería difícil y podría llevar décadas adentrarse en los rincones distantes de los mundos C y C ++. Me decepciona que no todos los compiladores adviertan contra códigos como ++ i + i ++. Del mismo modo, el orden de evaluación de los argumentos no está especificado.

OMI, demasiadas "cosas" quedan sin definir, sin especificar, definidas para la implementación, etc. Sin embargo, es fácil de decir e incluso dar ejemplos, pero es difícil de solucionar. 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.


El comportamiento indefinido contra el comportamiento no especificado tiene una breve descripción de él.

Su resumen final:

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


El comportamiento indefinido es uno de esos aspectos del lenguaje C y C ++ que puede sorprender a los programadores que vienen de otros lenguajes (otros lenguajes intentan ocultarlo mejor). Básicamente, es posible escribir programas en C ++ que no se comporten de una manera predecible, ¡aunque muchos compiladores de C ++ no reporten ningún error en el programa!

Veamos un ejemplo clásico:

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

La variable p apunta a la cadena literal "hello!/n" , y las dos asignaciones siguientes intentan modificar esa cadena literal. ¿Qué hace este programa? De acuerdo con la sección 2.14.5, párrafo 11 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 compilar esto sin problemas y obtener el resultado en yellow " o "¿Qué quieres decir con indefinido? Los literales de cadena se almacenan en la memoria de solo lectura, por lo que el primer intento de asignación da como resultado un volcado de memoria" . Este es exactamente el problema con un comportamiento indefinido. Básicamente, el estándar permite que ocurra cualquier cosa una vez que invocas un comportamiento indefinido (incluso los demonios nasales). Si hay un comportamiento "correcto" de acuerdo con su modelo mental del lenguaje, ese modelo es simplemente incorrecto; El estándar de C ++ tiene el único voto, punto.

Otros ejemplos de comportamiento indefinido incluyen acceder a una matriz más allá de sus límites, eliminar la referencia al puntero nulo , acceder a objetos una vez finalizada su vida útil o escribir expresiones supuestamente inteligentes como i++ + ++i .

La sección 1.9 de la norma C ++ también menciona a dos hermanos menos peligrosos del comportamiento indefinido, el comportamiento no especificado y el comportamiento definido por la implementación :

Las descripciones semánticas en esta Norma 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 deberá incluir documentación que describa sus características y comportamiento en estos aspectos.

Ciertos 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 los argumentos de una función). Donde sea posible, esta norma internacional define un conjunto de comportamientos permitidos. Estos definen los aspectos no deterministas de la máquina abstracta.

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

Específicamente, la sección 1.3.24 establece:

El comportamiento indefinido permisible va desde ignorar la situación completamente con resultados impredecibles , a 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 la emisión de un mensaje de diagnóstico).

¿Qué puedes hacer para evitar encontrar un comportamiento indefinido? Básicamente, tienes que leer buenos libros de C ++ de autores que saben de qué están hablando. Tornillo de tutoriales de internet. Tornillo bullschildt.