2010-01-12 14 views
5

Como entiendo por mi lectura, el comportamiento indefinido es el resultado de dejar al compilador con varias alternativas no idénticas en el momento de la compilación. Sin embargo, ¿no significaría eso que si se siguiera una práctica de codificación estricta (como poner cada asignación y cada igualdad en una declaración separada, depuración y comentarios adecuados) entonces no debería plantear un problema significativo para encontrar la fuente de lo indefinido? -comportamiento.¿Limita la confusión causada por el comportamiento indefinido?

Además, para cada error que aparece, si identifica el código, debe saber qué instrucciones se pueden usar en la declaración de esa declaración en particular, ¿correcto?

EDITAR: No estoy interesado en lugares donde haya escrito un código que no haya querido escribir. Me interesan los ejemplos en los que el código que funciona con lógica matemática no funciona.

Además, considero que las "buenas prácticas de codificación" son comentarios informativos fuertes cada pocas líneas, sangría adecuada y volcados de depuración de manera regular.

+0

¿Puedes dar un ejemplo de "código que suena por lógica matemática [que] no funciona?" –

+0

La práctica estricta de codificación no es suficiente, a menudo es una interacción inesperada de varias cosas lo que causa UB --- una vez que superas los conceptos básicos, como no usar punteros una vez liberados. –

+0

Entonces, ¿sería correcto suponer que si sigo las especificaciones del lenguaje y no cometí errores al definir lo que quería que el programa hiciera, y no hice nada estúpido, entonces mi programa debería funcionar como se esperaba? –

Respuesta

10

comportamiento indefinido no es necesariamente dejando el compilador con múltiples alternativas. Lo más común es simplemente hacer algo que no tiene sentido.

Por ejemplo, tomemos este código:

int arr[2]; 
arr[200] = 42; 

este es un comportamiento indefinido. No es que al compilador se le dieran múltiples alternativas para elegir. es solo que lo que estoy haciendo no tiene sentido. Idealmente, no debería permitirse en primer lugar, pero sin una comprobación de tiempo de ejecución potencialmente costosa, no podemos garantizar que algo como esto no ocurra en nuestro código. Entonces en C++, la regla es simplemente que el lenguaje especifica solo el comportamiento de un programa que se apega a las reglas. Si hace algo erróneo como en el ejemplo anterior, es simplemente undefined lo que debería suceder.

Ahora imagine cómo va a detectar este error. ¿Cómo va a salir a la superficie? Podría nunca parecen causar ningún problema. Tal vez sucede que escribimos en la memoria que está asignada al proceso (por lo que no obtenemos una infracción de acceso), pero nunca se usa (por lo que ninguna otra parte del programa leerá nuestro valor basura, ni sobrescribirá lo que escribimos)) Entonces parecerá que el programa está libre de errores y funciona bien.

O podría golpear una dirección que ni siquiera está asignada a nuestro proceso. Entonces el programa se bloqueará de inmediato.

O podría golpear una dirección que está asignada a nuestro proceso, pero en algún momento posterior se utilizará para algo. Entonces, todo lo que sabemos es que tarde o temprano, la función que lee de esa dirección tendrá un valor inesperado y se comportará de manera extraña. Esa parte es fácil de detectar en el depurador, pero no nos dice nada acerca de cuando o de donde ese valor de basura se escribió.Por lo tanto, no hay una forma sencilla de rastrear el error hasta su origen.

+0

espera ... Pensé que el comportamiento indefinido era distinto de un error de programación ... Es decir, algo así no es lo que yo consideraría una ambigüedad en las reglas. Lo definiría como mi propia estupidez. Aparte de eso, vi un ejemplo en el que una declaración de asignación triple daba diferentes valores en diferentes máquinas. Tengo más curiosidad sobre este tipo de comportamiento indefinido. –

+1

Son conceptos diferentes."Comportamiento indefinido" es simplemente "cualquier cosa que el estándar de lenguaje C++ no especificó". Un ejemplo clásico es "lo que debería suceder si escribo fuera de los límites de una matriz". En general, es un error del programador * confiar * en un comportamiento indefinido. El comportamiento indefinido en sí mismo no es correcto o incorrecto. Pero si ocurre en tu programa, tienes un problema. – jalf

+1

En cuanto a su ejemplo de asignaciones múltiples, se aplica lo mismo. El problema es que el estándar no especifica cómo se debe evaluar. Eso obviamente significa que es un error confiar en cualquier comportamiento específico de ese código. El comportamiento indefinido no se debe a la ambigüedad en las reglas. Es simplemente la ausencia de reglas. Las reglas especifican lo que debería suceder si haces * una * asignación en una declaración. Si haces dos, simplemente no tienen nada que decir. No es un conflicto entre dos posibles interpretaciones de las reglas. Simplemente * are * no hay reglas que apliquen. – jalf

1

No estoy seguro de si hay una definición formal de "comportamiento indefinido", pero seguir los buenos estándares de codificación puede reducir la ambigüedad y conducir a menos defectos de compilación y tiempo de ejecución.

Sin embargo, obtener dos programadores para acordar qué "buenos estándares de codificación" es un proceso complicado y propenso a errores.

Para su segunda pregunta, sí compiladores generalmente salida de un código de error que se puede utilizar para solucionar el problema

+1

"definición formal de 'comportamiento indefinido'" me hizo reír un poco ... –

+1

El estándar define * comportamiento indefinido * y generalmente dice explícitamente "no hacer X" o "hacer Y" es UB. Comprender esas situaciones y reconocer cuándo aparecen en tu código es otra cuestión. :) –

+1

_1.3.12 undefined behaviour_ es parte del estándar de idioma. Creo que esa es una definición tan formal como es de esperar. –

1

Como entiendo por mi lectura, el comportamiento indefinido es el resultado de dejar el compilador con varias alternativas no idénticas en tiempo de compilación.

Mientras que puede ser uno fuente de un comportamiento indefinido, que está hablando demasiado abstracta. Necesita un ejemplo específico de lo que quiere decir con "alternativas no idénticas en tiempo de compilación".

Si "sigue la práctica de codificación estricta", quieres decir que no utilizas la lógica que da como resultado un comportamiento indefinido, entonces sí (porque no habría un comportamiento indefinido). Rastrear un error debido a un comportamiento indefinido puede o no ser más fácil que seguir uno causado por un error de lógica.

Tenga en cuenta que el código que da como resultado un "comportamiento indefinido" sigue siendo un código legal de C++. Considero que es una clase de código/lógica que solo debería usarse muy raramente, cuando ese "comportamiento indefinido" es predecible para un programa dado en una plataforma dada con una implementación determinada. Encontrará casos en los que lo que el lenguaje considera "comportamiento indefinido" se definirá de hecho para un ambiente particular/conjunto de restricciones.

+0

Es "código legal de C++"; sin embargo, si UB ocurre en cualquier momento en el programa, la norma no garantiza el comportamiento de ese programa, * incluso si el UB ocurre después del comportamiento en cuestión. * –

+0

Estoy completamente de acuerdo. –

+0

La parte específica de la pregunta que citó describiría * comportamiento no especificado *, que es diferente de UB. –

7

En primer lugar, algunas definiciones de la norma C++ 03:

1.3.5 comportamiento definido por la implementación

Comportamiento, para una construcción de programa bien formada y corregir los datos, que depende sobre la aplicación y que cada aplicación debe documentar

1.3.12 comportamiento indefinido

Comportamiento, como podría surgir al usar una construcción de programa errónea o datos erróneos, para los cuales esta Norma Internacional no impone requisitos. También se puede esperar un comportamiento indefinido cuando esta Norma Internacional omite la descripción de cualquier definición o comportamiento explícito.

1.3.13 comportamiento no especificado

comportamiento, por una construcción programa bien formada y corregir los datos, que depende de la aplicación. La implementación no es necesaria para documentar qué comportamiento ocurre.

A pesar de que el comportamiento no especificado podría llamarse UB, nunca he visto eso, y UB siempre significa comportamiento indefinido. En toda la norma hay declaraciones similares a "hacer X es un comportamiento indefinido", pero a veces se topa con un caso que simplemente no está cubierto.

Para poner la definición de otra manera, si tiene un comportamiento indefinido en cualquier lugar, entonces todas las apuestas están desactivadas. En lo que respecta al estándar, su programa podría hacer cualquier cosa, desde invitar a su suegra a fin de semana de SuperBowl al running nethack. Debido a la naturaleza de UB, no se puede probar y no se puede esperar ninguna ayuda del compilador. (Aunque para algunos errores triviales, los compiladores generalmente producen diagnósticos.)

Por lo general, algo se define como UB porque simplemente no tiene sentido lógicamente (p.acceder a una matriz fuera de límites), pero también a menudo porque requeriría que la implementación hiciera demasiado trabajo para evitar — a menudo en tiempo de ejecución. Recuerde que C++ se deriva de C, y ser capaz de producir programas altamente optimizados es un objetivo principal de ambos idiomas. Con este fin, los idiomas se remiten al programador para asegurarse de que el código sea correcto en estas situaciones, relacionado con el principio "no pagas por lo que no usas".

Así que, finalmente, UB es malo, muy malo; Evitar a toda costa. Sin embargo, la parte difícil de UB es no saber qué es ni en qué circunstancias ocurre; la parte difícil es reconocer cuando invocas a UB. Por ejemplo:

std::string s = "abc"; 
char& c = s[0]; 
cout.write(s.data(), s.length()); 
c = '-'; 

Parece perfectamente razonable, ¿no? No, esto es UB, pero funcionará como esperabas en todas las implementaciones populares.

+0

De acuerdo, me rindo: ¿por qué su ejemplo 'std :: string :: data()' invoca a UB? El puntero devuelto por 'std :: string :: data()' es válido hasta la siguiente operación no const en la cadena, pero en el momento de modificarlo, 'cout.write()' ya está hecho con él. (Probablemente esté malinterpretando algo, mi copia del estándar C++ 98 dice que 'std :: basic_string :: operator [] (pos)' es equivalente a 'std :: basic_string :: data() [pos]', pero La descripción de 'data()' dice que los programas no pueden modificar la matriz de caracteres devuelta, lo que solo hace surgir la pregunta de por qué 'operator []' puede devolver una referencia no constante) – jamesdlin

+1

21.3/5: "References. .. refiriéndose a los elementos .. puede ser invalidado por .. llamando datos() .. " –

+0

Y sí, esa definición de op [] es problemática y hay un informe de defecto al respecto, pero no por la constness (que siempre ha sido una Un tema bastante trivial para mí, es simplemente decir que 's.data() [n]' y 's [n]' son el mismo objeto en esos casos). –

Cuestiones relacionadas