programar programación programacion programa primer para ingenieros hacer ejemplos dev curso como c++ undefined-behavior

c++ - programación - ¿Cómo explicar el comportamiento indefinido a los novatos sabelotodo?



programacion c++ ejemplos (18)

Hay un puñado de situaciones que el estándar de C ++ atribuye como comportamiento indefinido. Por ejemplo, si asigno con new[] , entonces intento liberar con delete (no delete[] ) ese comportamiento indefinido - cualquier cosa puede suceder - podría funcionar, podría colapsar de manera desagradable, podría corromper algo en silencio y plantear un problema temporizado.

Es tan problemático explicar que cualquier cosa puede pasarle a los novatos. Comienzan a "probar" que "esto funciona" (porque realmente funciona en la implementación C ++ que usan) y preguntan "¿qué podría estar mal con esto?" ¿Qué explicación concisa podría darles para motivarlos a no escribir tal código?


"Felicidades, has definido el comportamiento que tiene el compilador para esa operación. Espero que el informe sobre el comportamiento que muestran los otros 200 compiladores que existen en el mundo esté sobre mi escritorio mañana a las 10 AM. No defraudes. ¡Ahora, tu futuro parece prometedor!


C ++ no es realmente un lenguaje para los diletantes, y simplemente enumerar algunas reglas y hacer que obedezcan sin cuestionamientos hará que algunos programadores terribles; la mayoría de las cosas más estúpidas que veo que la gente dice están probablemente relacionadas con este tipo de reglas ciegas que siguen / abogando.

Por otro lado, si saben que los destructores no serán llamados, y posiblemente otros problemas, se ocuparán de evitarlo. Y, lo que es más importante, tenga alguna posibilidad de depurarlo si alguna vez lo hacen por accidente, y también de tener la oportunidad de darse cuenta de cuán peligrosas pueden ser muchas de las características de C ++.

Debido a que hay muchas cosas de qué preocuparse, ningún curso o libro hará que alguien domine C ++ o incluso se convierta en tan bueno con él.


Compila y ejecuta este programa:

#include <iostream> class A { public: A() { std::cout << "hi" << std::endl; } ~A() { std::cout << "bye" << std::endl; } }; int main() { A* a1 = new A[10]; delete a1; A* a2 = new A[10]; delete[] a2; }

Al menos cuando se usa GCC, muestra que al destructor solo se le llama para uno de los elementos cuando se hace una sola eliminación.

Acerca de la eliminación simple en las matrices POD. cppcheck a C ++ FAQ o haga que ejecuten su código a través de cppcheck .


Convierte a la persona en un puntero. Dígales que son un puntero a un humano de clase y está invocando la función ''RemoveCoat''. Cuando están apuntando a una persona y diciendo ''RemoveCoat'' todo está bien. Si la persona no tiene un abrigo, no se preocupe; lo comprobamos, todo lo que RemoveCoat realmente hace es quitar la capa superior de la ropa (con controles de decencia).

Ahora, ¿qué sucede si apuntan a un lugar al azar y dicen RemoveCoat? Si apuntan a una pared, la pintura podría despegarse, si apuntan a un árbol, la corteza podría desprenderse, los perros podrían afeitarse, el USS Enterprise podría bajar sus escudos en un momento crítico, etc.

No hay forma de averiguar qué podría pasar, el comportamiento no se ha definido para esa situación; esto se denomina comportamiento indefinido y debe evitarse.


Cuénteles sobre los estándares y cómo se desarrollan las herramientas para cumplir con los estándares. Cualquier cosa fuera del estándar podría funcionar o no, lo cual es UB.


Déjelos probar su camino hasta que su código se bloquee durante la prueba. Entonces las palabras no serán necesarias.

El caso es que los novatos (todos hemos estado allí) tienen cierta cantidad de ego y confianza en sí mismos. Está bien. De hecho, no podrías ser un programador si no lo hicieras. Es importante educarlos, pero no menos importantes para apoyarlos y no cortar su inicio en el viaje al socavar su confianza en sí mismos. Solo sea cortés pero demuestre su posición con hechos, no con palabras. Solo los hechos y la evidencia funcionarán.


Dos posibilidades vienen a mi mente:

  1. Podrías preguntarles "solo porque puedes conducir en la autopista en la dirección opuesta a la medianoche y sobrevivir, ¿lo harías regularmente?"

  2. La solución más complicada podría ser configurar un entorno de compilación / ejecución diferente para mostrarles cómo falla espectacularmente en diferentes circunstancias.


El hecho de que su programa parezca funcionar no es garantía de nada; el compilador podría generar código que funcione (¿cómo se define "trabajo" cuando el comportamiento correcto no está definido ?) los días de semana, pero formatea el disco los fines de semana. ¿Leyeron el código fuente a su compilador? Examine su salida desmontada?

O recuérdeles simplemente porque sucede que "funciona" hoy no es garantía de que funcione cuando actualiza su versión del compilador. Diles que se diviertan encontrando cualquier error sutil que surja de eso.

Y realmente, ¿por qué no ? Deben proporcionar un argumento justificable para usar un comportamiento indefinido, y no al revés. ¿Qué razón hay para usar delete lugar de delete[] aparte de la pereza? (De acuerdo, hay std::auto_ptr . Pero si estás usando std::auto_ptr con una new[] matriz asignada new[] , probablemente deberías estar usando std::vector todos modos).


Encienda malloc_debug y delete una matriz de objetos con destructores. free un puntero dentro del bloque debería fallar. Llámalos a todos juntos y demuéstralo.

Tendrá que pensar en otros ejemplos para construir su credibilidad hasta que entiendan que son novatos y que hay mucho que saber sobre C ++.


Explicaría que si no escribieran el código correctamente, su próxima revisión de desempeño no sería feliz. Eso es suficiente "motivación" para la mayoría de las personas.


Indefinido significa explícitamente no confiable. El software debe ser confiable. No deberías tener que decir mucho más.

Un estanque helado es un buen ejemplo de una superficie para caminar indefinida. Solo porque lo cruces una vez no significa que debas agregar el atajo a tu ruta en papel, especialmente si planeas las cuatro estaciones.


Me gusta esta cita:

Comportamiento no definido: puede dañar sus archivos, formatear su disco o enviar correos de odio a su jefe.

No sé a quién atribuir esto (¿tal vez es de Effective C ++ )?


Silenciosamente anule nuevo, nuevo [], elimine y elimine [] y vea cuánto tiempo tarda en darse cuenta;)

Si falla, solo dile que está equivocado y apúntalo hacia la especificación de C ++. Ah, sí ... ¡y la próxima vez tenga más cuidado al contratar gente para asegurarse de evitar los agujeros!


Simplemente cite del estándar. Si no pueden aceptar eso, no son programadores de C ++. ¿Los cristianos negarían la Biblia? ;-)

1.9 Ejecución del programa

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

  2. 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 . [...]

  3. 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. [...]

  4. 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 programas que contienen un comportamiento indefinido . -Finalizar nota]

No puedes ser más claro que eso.


Solo muéstrales Valgrind.


Un punto aún no mencionado sobre el comportamiento indefinido es que si realizar alguna operación daría como resultado un comportamiento indefinido, una implementación conforme a los estándares podría legítimamente, quizás en un esfuerzo de ser "útil" o mejorar la eficiencia, generar código que fallaría si tal operación fueron intentados Por ejemplo, uno puede imaginar una arquitectura multiprocesador en la que cualquier ubicación de memoria puede estar bloqueada, e intentar acceder a una ubicación bloqueada (excepto para desbloquearla) se detendrá hasta que se desbloquee la ubicación en cuestión. Si el bloqueo y el desbloqueo fueran muy baratos (plausible si se implementan en hardware) una arquitectura así podría ser útil en algunos escenarios de subprocesos múltiples, desde la implementación de x++ como (atómicamente leer y bloquear x; agregar uno para leer valor; desbloquear atómicamente y escribir x) aseguraría que si dos subprocesos realizaban simultáneamente x++ , el resultado sería agregar dos a x. Siempre que los programas estén escritos para evitar un comportamiento indefinido, una arquitectura de este tipo podría facilitar el diseño de un código confiable de múltiples subprocesos sin requerir grandes barreras de memoria torpes. Desafortunadamente, una declaración como *x++ = *y++; podría causar interbloqueo si y eran ambas referencias a la misma ubicación de almacenamiento y el compilador intentó canalizar el código como t1 = read-and-lock x; t2 = read-and-lock y; read t3=*t1; write *t2=t3; t1++; t2++; unlock-and-write x=t1; write-and-unlock y=t2; t1 = read-and-lock x; t2 = read-and-lock y; read t3=*t1; write *t2=t3; t1++; t2++; unlock-and-write x=t1; write-and-unlock y=t2; . Si bien el compilador podría evitar un punto muerto al abstenerse de intercalar las diversas operaciones, hacerlo podría impedir la eficiencia.


Uno sería ...

"Este" uso no es parte del lenguaje. Si decimos que en este caso el compilador debe generar código que falla, entonces sería una característica, algún tipo de requisito para el fabricante del compilador. Los redactores del estándar no quisieron dar trabajo innecesario a las "características" que no son compatibles. Decidieron no hacer ningún requisito de comportamiento en tales casos.


John Woods :

En resumen, no puedes usar sizeof () en una estructura cuyos elementos no han sido definidos, y si lo haces, los demonios pueden salir volando de tu nariz.

"Los demonios pueden volar fuera de tu nariz" simplemente debe ser parte del vocabulario de cada programador.

Más al punto, habla de portabilidad. Explique cómo los programas tienen que ser portados con frecuencia a diferentes sistemas operativos, y mucho menos a diferentes compiladores. En el mundo real, los puertos generalmente los hacen personas que no son los programadores originales. Algunos de estos puertos son incluso para dispositivos integrados, donde puede haber enormes costos al descubrir que el compilador decidió de manera diferente a su suposición.