¿Qué es el comportamiento indefinido 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?
530
¿Qué es el comportamiento indefinido 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?
Respuestas:
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 manera predecible, ¡aunque muchos compiladores de C ++ no informarán ningún error en el programa!
Veamos un ejemplo clásico:
La variable
p
apunta al literal de cadena"hello!\n"
, y las dos asignaciones a continuación intentan modificar ese literal de cadena. ¿Qué hace este programa? De acuerdo con la sección 2.14.5, párrafo 11 del estándar C ++, invoca un comportamiento indefinido :Puedo escuchar a la gente gritar "Pero espera, puedo compilar esto sin problemas y obtener el resultado
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 resulta en un volcado del núcleo". Este es exactamente el problema con el comportamiento indefinido. Básicamente, el estándar permite que cualquier cosa suceda una vez que invocas un comportamiento indefinido (incluso demonios nasales). Si hay un comportamiento "correcto" de acuerdo con su modelo mental del lenguaje, ese modelo simplemente está equivocado; 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, desreferenciar el puntero nulo , acceder a objetos después de que finalice su vida útil o escribir expresiones supuestamente inteligentes como
i++ + ++i
.La Sección 1.9 del estándar C ++ también menciona los dos hermanos menos peligrosos del comportamiento indefinido, el comportamiento no especificado y el comportamiento definido por la implementación :
Específicamente, la sección 1.3.24 establece:
¿Qué puede hacer para evitar tener un comportamiento indefinido? Básicamente, tienes que leer buenos libros de C ++ de autores que saben de lo que están hablando. Tornillo tutoriales de internet. Tornillo bullschildt.
fuente
int f(){int a; return a;}
: el valor dea
puede cambiar entre llamadas a funciones.Bueno, esto es básicamente un simple copiar y pegar del estándar
fuente
int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; }
un compilador puede determinar que, dado que todos los medios para invocar la función que no ejecuta los misiles invocan Comportamiento indefinido, puede hacer que la llamada sealaunch_missiles()
incondicional.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 vendedores del compilador especifican qué tamaños usarán y proporcionan una documentación de lo que hicieron.
comportamiento indefinido
Estás haciendo algo mal. Por ejemplo, tiene un valor muy grande en un
int
que no encajachar
. ¿Cómo se pone ese valorchar
? en realidad no hay manera! Podría pasar cualquier cosa, pero lo más sensato sería tomar el primer byte de ese int y ponerlochar
. 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?
¡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 su pregunta vale la pena editar la respuesta para aclarar más :)
La diferencia entre implementación definida y 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 se puede decir quesizeof(int)
es 4 para una parte del programa y 8 para otras. A diferencia del comportamiento no especificado, donde el compilador puede decir OK, evaluaré estos argumentos de izquierda a derecha y los argumentos de la siguiente función se evaluarán 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 especificaran algunos de los comportamientos no especificados. Eche un vistazo aquí a la respuesta del Dr. Stroustrup para eso :fuente
fun(fun1(), fun2());
no es el comportamiento"implementation defined"
? ¿El compilador tiene que elegir uno u otro curso, después de todo?"I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left"
entiendo que estocan
suceda. ¿Realmente, con los compiladores que usamos en estos días?Del documento oficial de Justificación C
fuente
El comportamiento indefinido frente al comportamiento no especificado tiene una breve descripción.
Su resumen final:
fuente
Históricamente, tanto el Comportamiento definido por la implementación como el Comportamiento indefinido representaban situaciones en las que los autores de la Norma esperaban que las personas que escriben implementaciones de calidad usarían el 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 objetivos 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 brindan flexibilidad a los escritores de compiladores 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. Sin embargo, las implementaciones de calidad que afirman ser adecuadas para un propósito particular, deben comportarse de una manera apropiada para dicho propósito.si la Norma lo requiere 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 la implementación podría hacer sería útil . La línea divisoria entre ellos no es si en general sería útil para las implementaciones definir comportamientos (los escritores de compiladores deberían definir comportamientos útiles cuando sea práctico si el Estándar 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 podrían existir no implica, de ninguna manera 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 compiladores 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 los campos de aplicación donde son vitales, e incluso en sistemas donde prácticamente no cuestan nada. En lugar de tratar a UB como una invitación a ejercer un juicio razonable, los escritores de compiladores han comenzado a tratarlo como una excusa para no hacerlo.
Por ejemplo, dado el siguiente código:
una implementación de complemento a dos no tendría que hacer ningún esfuerzo para tratar la expresión
v << pow
como un cambio de complemento a dos sin tener en cuenta siv
fue positiva o negativa.La filosofía preferida entre algunos de los escritores de compiladores de hoy, sin embargo, sugeriría que debido a que
v
solo puede ser negativo si el programa va a participar en Comportamiento indefinido, no hay razón para que el programa recorte el rango negativov
. Aunque el desplazamiento hacia la izquierda de los valores negativos solía ser compatible con cada compilador significativo, y una gran cantidad de código existente depende de ese comportamiento, la filosofía moderna interpretaría el hecho de que el Estándar dice que los valores negativos hacia la izquierda son UB como implicando que los escritores del compilador deberían sentirse libres de ignorar eso.fuente
<<
UB tenga números negativos es una pequeña trampa desagradable y me alegra que me lo recuerden!i+j>k
produce 1 o 0 en los casos en que la adición se desborda, siempre que no tenga otros efectos secundarios , un compilador puede realizar algunas optimizaciones masivas que no serían posibles si el programador escribiera el código como(int)((unsigned)i+j) > k
.C ++ estándar n3337 § 1.3.10 comportamiento definido por la implementación
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). Por lo tanto, el usuario aún puede saber exactamente cómo se comportará el programa, aunque Standard no lo describa.
C ++ estándar n3337 § 1.3.24 comportamiento indefinido
Cuando el programa encuentra una construcción que no está definida de acuerdo con el Estándar C ++, se le permite hacer lo que quiera hacer (tal vez enviarme un correo electrónico o quizás enviarle un correo electrónico o ignorar el código por completo).
C ++ estándar n3337 § 1.3.25 comportamiento no especificado
C ++ Standard no impone un comportamiento particular en algunas construcciones, sino que dice que se debe elegir un comportamiento particular y bien definido ( no se describe necesariamente 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 programa.
fuente
Implementación definida
Sin especificar
Indefinido
fuente
uint32_t s;
, se podría esperar que evaluar1u<<s
cuándos
es 33 pueda producir 0 o tal vez 2, pero no hacer nada más raro. Sin embargo, los compiladores más nuevos1u<<s
pueden hacer que un compilador determine que debido a ques
debe haber sido inferior a 32 de antemano,s
se puede omitir cualquier código anterior o posterior a esa expresión que solo sería relevante si hubiera sido 32 o superior.