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

Resuelto Zolomon asked hace 14 años • 9 respuestas

¿Qué es el comportamiento indefinido (UB) en C y C++? ¿Qué pasa con el comportamiento no especificado y el comportamiento definido por la implementación ? ¿Cuál es la diferencia entre ellos?

Zolomon avatar Mar 08 '10 04:03 Zolomon
Aceptado

El comportamiento indefinido es uno de esos aspectos del lenguaje C y C++ que puede resultar sorprendente para 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 forma predecible, ¡aunque muchos compiladores de C++ no informarán 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 papunta al literal de cadena "hello!\n"y las dos asignaciones siguientes intentan modificar ese literal de cadena. ¿Qué hace este programa? Según el estándar C++, [lex.string] nota 4 , invoca un comportamiento indefinido :

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

Puedo escuchar a la gente gritar "Pero espera, puedo compilar esto sin problemas y obtener el resultado yellow" o "¿Qué quieres decir con indefinidos? 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 del núcleo". Este es exactamente el problema con el comportamiento indefinido. Básicamente, el estándar permite que suceda cualquier cosa una vez que se invoca un comportamiento indefinido (incluso demonios nasales). Si hay un comportamiento "correcto" según su modelo mental del lenguaje, ese modelo simplemente es incorrecto; El estándar C++ tiene el único voto, punto.

Otros ejemplos de comportamiento indefinido incluyen

  • acceder a una matriz más allá de sus límites ,
  • división por cero ,
  • desreferenciar un puntero nulo ,
  • acceder a objetos una vez finalizada su vida útil , o
  • escribiendo expresiones supuestamente inteligentes como i++ + ++i.

[intro.defs] también define los dos hermanos menos peligrosos del comportamiento indefinido, el comportamiento no especificado y el comportamiento definido por la implementación :

comportamiento definido por la implementación     [defns.impl.definido]

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

comportamiento no especificado     [defns.unspecified]

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

[ Nota : No es necesario que la implementación documente qué comportamiento se produce. La gama de posibles comportamientos suele estar delineada en este documento. - nota final ]

comportamiento indefinido     [defns.undefinido]

comportamiento para el cual este documento no impone requisitos

[ Nota : Se puede esperar un comportamiento indefinido cuando este documento omite cualquier definición explícita de comportamiento o cuando un programa utiliza una construcción errónea o datos erróneos. El comportamiento indefinido permitido varía desde ignorar completamente la situación 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 finalizar una traducción o ejecución (con la emisión de un mensaje de diagnóstico). [...] - nota final ]

¿Qué puedes hacer para evitar encontrarte con un comportamiento indefinido? Básicamente, debes leer buenos libros en C++ escritos por autores que sepan de lo que hablan. Evite los tutoriales en Internet. Evite los bullschildt.

fredoverflow avatar Nov 05 '2010 10:11 fredoverflow

Bueno, esto es básicamente copiar y pegar directamente del estándar C :

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 indefinido , tras el uso de un programa no portátil o erróneo o de datos erróneos, para el cual esta Norma Internacional no impone requisitos

2 NOTA El posible comportamiento indefinido varía desde ignorar completamente la situación 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 finalizar 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 caso de 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 cuál se elige en cualquier caso

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

AnT stands with Russia avatar Mar 07 '2010 21:03 AnT stands with Russia

Quizás una redacción más sencilla podría ser más fácil de entender que una definición rigurosa de las normas.

Comportamiento definido por la implementación:
el lenguaje dice que tenemos tipos de datos. Los proveedores de compiladores especifican qué tamaños utilizarán y proporcionan documentación de lo que hicieron.

Comportamiento indefinido:
estás haciendo algo mal. Por ejemplo, tienes un valor muy grande en un intque no encaja char. ¿ Cómo se pone ese valor char? ¡En realidad no hay manera! Podría pasar cualquier cosa, pero lo más sensato sería coger el primer byte de ese int y meterlo en char. Simplemente está mal hacer eso para asignar el primer byte, pero eso es lo que sucede bajo el capó.

Comportamiento no especificado:
¿Cuál de estas dos funciones se ejecuta primero?

void fun(int n, int m);

int fun1() {
    std::cout << "fun1";
    return 1;
}
int fun2() {
    std::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 resultar o no en un comportamiento indefinido, pero ciertamente su programa no debería producir un comportamiento no especificado.


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

¿ No fun(fun1(), fun2());está el comportamiento "definido por implementación"? ¿Después de todo, el compilador tiene que elegir uno u otro camino?

La diferencia entre definido por implementación y no especificado es que se supone que el compilador debe elegir un comportamiento en el primer caso, pero no es necesario en el segundo caso. Por ejemplo, una implementación debe tener una y sólo una definición de sizeof(int). Por lo tanto, no se puede 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 hecho más fácil 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 se puede producir dando al compilador esta libertad y lo que requiere una "evaluación ordinaria de izquierda a derecha" puede ser significativa. No estoy convencido, pero con innumerables compiladores "por ahí" aprovechando la libertad y algunas personas defendiendo apasionadamente esa libertad, un cambio sería difícil y podría llevar décadas penetrar hasta 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, no se especifica el orden de evaluación de los argumentos.

En mi opinión, demasiadas "cosas" quedan sin definir, sin especificar, eso es fácil de decir e incluso de dar ejemplos, pero difícil de solucionar. También cabe señalar que no es tan difícil evitar la mayoría de los problemas y producir código portátil.

Khaled Alshaya avatar Mar 07 '2010 21:03 Khaled Alshaya

Del documento oficial de justificación de C

Los términos comportamiento no especificado , comportamiento no definido y comportamiento definido por la implementación se utilizan para categorizar el resultado de escribir programas cuyas propiedades el Estándar no describe o no puede describir completamente. El objetivo de adoptar esta categorizació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 sello de conformidad con el Estándar. El Apéndice F de la Norma cataloga aquellos comportamientos que caen en una de estas tres categorías.

El comportamiento no especificado le da al implementador cierta libertad para traducir programas. Esta libertad no llega hasta el punto de no traducir el programa.

El comportamiento indefinido otorga al implementador licencia para no detectar ciertos errores del programa que son difíciles de diagnosticar. También identifica áreas de posible extensión del lenguaje conforme: el implementador puede aumentar el lenguaje proporcionando 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 se explique 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.

Johannes Schaub - litb avatar Jan 23 '2013 18:01 Johannes Schaub - litb