Domina el arte de depurar C++ Builder
Escribir código conciso
En primer lugar, y quizás lo más importante, es extremadamente importante escribir código conciso y legible. Si puede volver a un fragmento de código después de haberlo escrito y comentarlo para explicar qué hace y por qué, puede ahorrarse innumerables horas de doloroso seguimiento del código que está escribiendo. Tal vez dedique un poco más de tiempo a escribir código, pero cuando haya pasado N horas rastreando esos errores esquivos, estará de acuerdo en que vale la pena dedicar un poco más de tiempo a hacer que el código de su programa sea legible. (Podrías haber eliminado fácilmente el error). Si aún no lo ha hecho, le sugiero que se detenga y lea el otro excelente artículo de Scott: Coding Style (que traduciremos tan pronto como lo necesite).
Uso de excepciones y capacidades de manejo de excepciones
Ahora vamos al siguiente paso, que sigue siendo un paso basado en código. (Salvo en casos excepcionales, no siempre puedes utilizar el depurador integrado de tu sistema, por lo que siempre es una buena idea conocer otras formas de encontrar estos molestos errores). Este paso trata sobre cómo hacerlo y, lo que es más importante, cómo manejar los errores (generados) generados por el sistema cuando su formulario genera una excepción. En los días oscuros antes de que se reconociera el estándar C++, las aplicaciones a menudo indicaban errores devolviendo un valor (este método todavía se usa en ole y algunas funciones winapi). Obviamente, puedes ignorar fácilmente estos errores (de hecho, lo haces a menudo, quiero decir, ¿cuántas veces has reflexionado sobre el valor de retorno de una función winapi?
Entonces decidieron: "Está bien, bueno,
Entonces decidieron: "Está bien, necesitamos un nuevo mecanismo, uno que no puedas ignorar. Pero puedes personalizarlo con ) para manejarlo. Ahí es donde entra en juego la excepción. ¿Quieres un indicador de tipo de error especial? Simple, define un nuevo tipo de excepción (es solo una clase, nada más), tíralo (genera la excepción). miexcepción
{
público:
imessage ansistring;
miexcepción(mensaje ansistring) { imessage= mensaje;}
};
throw new myexception("testException message");
¡Es así de simple! (Por supuesto, esto no está completo, lo agregaré pronto). , puede preguntar: "Puedo lanzar excepciones ahora, pero ¿cómo las manejo? Quiero decir, primero quiero eliminarlas del código" Excepción "¡Es ciertamente fácil de hacer y, de hecho, fácil de personalizar! El comité de estándares". definimos el mecanismo try {/* code*/ } catch (...) {/* code*/ } para nosotros, que funciona con excepciones. El mecanismo es el mismo y se puede personalizar según sus necesidades. ¡Simplemente coloque el código de ejecución! en el módulo try. También necesitas un módulo catch() o __finally para decirle al programa qué hacer cuando encuentra una excepción. Esto es lo que haces ahora. La belleza de lo que haces: al declarar catch(), defines. una clase de tipo clase e ingrese las variables utilizadas para detectar la excepción.
(En el ejemplo anterior, se veía así: catch(myexception e) { /* escribe el código para manejar la excepción aquí */}) Para hacer que este sistema sea más robusto, puedes crear un árbol de continuación de subclases completo. De esta manera, cuando captura una clase base, captura todos los tipos de excepción que se heredan de esa clase base (en vcl, un buen ejemplo es que todas las excepciones se heredan de la clase de excepción, por lo que captura(excepción e) Todas las excepciones de vcl ser atrapado, incluidos los que generas excepto esocketerror, mira el howto de xiphias (que traduciré lo antes posible si no te gusta) (ten esta idea en mente para otro paso en este momento). Para hacerlo más poderoso, el comité de estándares decidió agregar la siguiente declaración catch(), sí, los tres puntos entre paréntesis prometen detectar cualquier excepción, es decir, todas las excepciones. ¿Por supuesto? , puede usar una instrucción catch() adicional, que se parece a if...else if. ¡Una cosa a tener en cuenta aquí! Si detecta un tipo de excepción, no se volverá a detectar. ¡Mire primero el siguiente código! p>
try
{
//Código de ejecución normal del programa
}
catch(edbengineerror e)
{
// Manejar errores basados en el motor de base de datos
}
catch(eexternalerrore)
{
//Generalmente maneja errores basados en Windows
}
catch (excepción e)
{
/ /Manejar todos los demás errores de vcl
}
Aquí puede ver que en "¿Es edbengineerror? Sí - manejar, ¿no? - continuar Captura ""¿Es eExternalError? Sí - manejar , ¿No? - Continuar con la siguiente captura" y así sucesivamente.
Hay más. Si desea hacer algo con la excepción pero no quiere que desaparezca, puede volver a generar (generar) la excepción. excepción. Continuará buscando un nuevo procedimiento catch() para manejarlo. No hago esto muy a menudo, pero es bueno saber que "lanzar" es lo que es. Ya ha manejado la excepción y continúa. mirando hacia atrás para encontrar otra captura que lo maneje
Por último, pero no menos importante (esta parte no está incluida en la especificación estándar, es más como una captura adicional específica de la región del Báltico) es el __finalmente). declaración, que es un módulo __finally{ } que le permite especificar el código que se ejecutará independientemente de si se produce una excepción. Esto es para borrar las variables locales asignadas a través del nuevo método y restablecer cualquiera. restaurarse a la normalidad (por ejemplo, para restablecer el puntero del mouse de espera a normal).
¡Ah, tantos! Tómate un descanso y echa un vistazo a las clases de excepción en la ayuda del constructor de C++ cuando tengas tiempo (todas las clases de excepción que comienzan con e, notarás que todas se heredan de la clase de excepción). ¡Este también es un buen ejercicio en clases de excepción personalizadas! ) regresa y pasaremos al siguiente paso.
Uso de mecanismos de registro
No siempre se puede utilizar un depurador para eliminar errores y, a veces, no se puede confiar en la potencia del depurador integrado, por lo que a veces es necesario recurrir a otras herramientas de depuración para depurar el programa. (Los ejemplos típicos son: servicios nt, programas isapi/cgi, aplicaciones en tiempo real, etc.) En este punto, debe utilizar técnicas de depuración/depuración de la vieja escuela de las que sólo hablan programadores experimentados como nosotros. Por ejemplo, utilice algún tipo de mecanismo de registro para ver qué sucede bajo el capó de su programa. Afortunadamente, existen muchos mecanismos disponibles para hacerlo más fácil. Aquí repasaré tres de mis métodos favoritos. También puedes enviarme el tuyo por correo electrónico y consideraré agregar esta sección.
Bien, comencemos con el primer método outputdebugstring. Afortunadamente, Microsoft ha implementado un subsistema de depuración/depuración muy extenso para nosotros. Esto incluye mecanismos para implementar su propio sistema de depuración/registro de depuración. Cuando el programa se ejecuta en el proceso de depuración/depuración, outputdebugstring enviará su argumento (cadena c) al contexto de salida del depurador; si el depurador no se está ejecutando, se ignorará outputdebugstring. Si la cadena de depuración de salida funciona bien en la terminal sin el mensaje emergente, no olvide eliminarla (a través de #ifdef debug#endif') antes de publicarla en el cliente para que el programa se ejecute un poco más rápido. "¡Vaya, qué simple y hermoso!", podrías decir: "¿Pero qué haces cuando el programa no se ejecuta dentro del depurador/depurador?
Recuerda, esto es solo mi opinión, se basa en Evaluación de percepción, yo personalmente uso la interfaz dbugint.pas proporcionada por gexperts para la depuración. Es un pequeño programa independiente excelente. De lo contrario, al igual que outputdebugstring, si no está instalado, en realidad no hará nada :) (prestará atención a). si el terminal está instalado en la máquina). Usar dbugint.pas también es simple, agréguelo a su proyecto y luego agregue #include "dbugintf.hpp." (debido a que es un archivo pascal, debe agregarlo a su proyecto). para que el compilador c++builder pueda generar el archivo de encabezado hpp). Luego, simplemente use senddebug("string to be sent to record" ); o, si desea ser más flexible, también puede usar senddebugex-add. un parámetro de tipo de mensaje para llamar a tmsgdlgtype (consulte la ayuda en línea de vcl para obtener instrucciones específicas). O, si desea ser más flexible, también puede usar senddebugex-add un parámetro de tipo de mensaje para llamar a tmsgdlgtype (consulte la ayuda en línea de vcl para obtener instrucciones); sendmethodenter, sendmethodexit, sendseparator, etc. Si planea proporcionar este terminal (gdebug.exe) a algunos usuarios finales, no olvide incluir los paquetes necesarios.
La tercera opción que quiero señalar. Probablemente lo más difícil es implementar su propia consola de registro. ¡Esto no es tan fácil como podría pensar! Agregue un control richedit al formulario, hágalo de solo lectura y luego comience a registrar, ¿verdad? ¡Equivocado! Esto está bien en teoría, pero en la práctica, iniciar sesión con el control richedit ralentiza el programa, fragmenta la memoria, pierde memoria y, a menudo, ralentiza toda la máquina en menos de 10 minutos. (Se necesita un tiempo para entender por qué, pero te lo prometo). Entonces, todo lo que necesita hacer es planificar lo que necesita para su mecanismo de registro y, si desea un ícono colorido, comenzar a planificar un control personalizado. Existe otro método que requiere un poco de trabajo pero que es muy efectivo. Utilice un control de cuadro de lista para registrar y establecer la propiedad de estilo en lbownerdrawfixed para que los identificadores se dibujen solos. (Esto es también lo que hacen gexperts y su consola gdebug). Aún queda mucho trabajo por hacer, pero jaja, por si quieres hacerlo
Combina el uso del mecanismo de registro con el mecanismo de manejo de excepciones de la clase
Ahora vaya al siguiente paso:)( ¡Apuesto a que nunca se dio cuenta de cuánto trabajo implica construir un gran sistema de depuración/depuración! )
Ahora, vaya al siguiente paso. No siempre es necesario predecir lo que sucederá con varias excepciones inesperadas y cuando el programa ha pasado por muchas pruebas de depuración (atacando el programa tanto como sea posible, tratando de hacerlo fallar), en la gran mayoría de los casos. No tienes que preocuparte por este problema en absoluto.
Aquí hay un consejo que recomiendo que cualquier desarrollador de componentes siga estrictamente al probar un nuevo componente/nuevo código en un IDE por primera vez. Debido a que las excepciones en el IDE pueden causar muchos problemas, a veces incluso reiniciar el IDE no ayuda (yo mismo me encontré con esto). En realidad es muy simple. Antes de cada función en tu código, o al menos antes y después de todas las funciones principales, agrega:
prueba
{
Código de interfaz de usuario
}
catch(Exception e)
{
senddebugmessage( "excepción detectada en nombre de clase::nombre de función de tipo:")+e.nombredeclase ( )
+" y muestra el mensaje."+e.message);
};
(y reemplaza el nombre de la clase y el nombre de la función en el cadena con nombre de clase y nombre de función de la función). De esta manera puede saber rápidamente dónde ocurrió la excepción sin tener que forzar el cierre del IDE.
Bien, es hora de revisar cómo nos ayuda el método classname(). No querrás recibir una cadena de "excepción" cada vez, ¿verdad? ¿Es porque e se declara como excepción? No. Esta es una de las partes más interesantes de vcl, cualquier clase que herede de tobject conoce activamente su propio tipo, el tipo de clase base y mucha otra información interesante, como puede ver en la ayuda de tobject. Entonces, incluso si usamos la excepción e, e.classname() puede encontrar el nombre de clase real de la excepción (polimorfismo de C++). El costo de estos beneficios es que el archivo ejecutable es mucho más grande, como encontrarán casi todos los programadores de C++builder/Delphi. (Sin dolor no hay ganancia) Sin dolor no hay ganancia. ellos han dicho.
xiphias agregó el método addline a tstringlist y el método savetofile es otra forma eficiente de registro. Finalmente, asegúrese de que su aplicación siempre escriba en el archivo de registro o reescriba el archivo de registro cada vez que se detecte una excepción.
Manejo de excepciones generadas fuera del código
Este paso es el último paso basado en código antes de comenzar a aprender sobre el depurador basado en banderas. Sin embargo, este es quizás el paso más importante para decorar su aplicación cuando ocurren errores críticos. Por ejemplo, este es un momento ideal para mostrar un cuadro de diálogo que contiene detalles del error. Este cuadro de diálogo aparece para permitir que los usuarios finales le informen de errores. Estoy seguro de que odias informes como "Oh, hay un cuadro de diálogo que muestra dónde ocurrió el error de excepción". En realidad, no es tan difícil implementar una solución mejor que no limite cómo planea manejarla. El primer paso es crear una función en el formulario principal (como el primer formulario en el formulario principal). Cree una función como esta:
void __fastcall applevelexceptionhandler(tobject *sender, excepción *e)
{
}
Luego agregue el código apropiado para mostrar el error (mensaje electrónico), tipo de error (recuerde e.classname(), siempre que sea e. classname()), formas específicas de contactarlo y cualquier otra cosa que desee agregar. El segundo paso es, por supuesto, vincularlo al sistema, lo cual se hace fácilmente en c++builder:
application-onexception=applevelexceptionhandler;
Agregue en el evento oncreate del formulario el código anterior. ¡No seas tacaño! Al agregar esta línea de código, básicamente tiene la garantía de no perder ninguna excepción, y no importa dónde falle su controlador de excepciones, ¡esta línea de código aparecerá frente a usted!
Tu turno
Ahora tienes toda la información útil.
Es hora de comenzar a agregarlos a sus proyectos actuales, olvidarse de ellos o hacerlos parte de su hábito de programación. ¡Esto es algo que estás feliz de aceptar!
En la siguiente parte de esta serie, analizaré cómo usar el depurador integrado para ver qué sucede mientras se ejecuta su programa, cómo recorrer el código, establecer puntos de interrupción, ver variables y todas las demás cosas que harían que un novato fuera una herramienta divertida y aterradora. Hasta entonces, sus errores pueden ser sólo errores.
Bien, (con un poco más de preparación) es hora de comenzar a rastrear y buscar errores que aún están ocultos en el código después de esfuerzos anteriores, es decir, comenzar a rastrear los errores que se marcaron en el código anterior. error/excepción. La primera es la etapa de preparación.
Preparación antes de depurar el archivo ejecutable
Antes de comenzar a depurar el archivo ejecutable, debemos asegurarnos de que ciertas configuraciones sean correctas en la mayoría de los casos. A continuación, analizaré cada una de estas configuraciones y explicaré brevemente por qué son necesarias. (Si está interesado en algunas de estas configuraciones, haga clic en el botón "Ayuda" para obtener más detalles). Comencemos ahora y abramos Proyecto|Opciones.
Opciones del proyecto
Primero, nos quedaremos en la pestaña Compilador. Simplemente haga clic en el botón "Depuración completa" y la mayoría de las otras configuraciones que necesitamos estarán listas. Establecer "Optimización de código" en "Ninguno" siempre es algo bueno, ya que esto esencialmente le dice al compilador que todo está hecho y solo genera código respirable. No hagas otras optimizaciones inteligentes sólo por el bien de la velocidad. (La ventaja de esto es que reduce en gran medida la dificultad de depuración. El código en el programa es exactamente el mismo que escribimos y no ha sido optimizado por el compilador. Seleccione "Información de depuración" (haga clic) en "Depurar "Panel, que debe estar configurado en el mensaje "Aceptar". También recomiendo marcar "Desactivar extensiones en línea". Las extensiones en línea son excelentes para publicar código, pero al depurar es mejor desactivarlas, solo le darán más dolores de cabeza.
Luego está la pestaña "Pascal", especialmente si tiene una unidad Pascal adjunta a su proyecto, o usa un control VCL basado en Pascal (si tiene su código fuente Pascal, el compilador lo recompilará activamente usando la configuración en esta sección). Aquí tienes que deshabilitar las opciones de optimización y luego generalmente dejo marcadas todas las opciones en la sección de depuración (marcada).
La siguiente pestaña es la pestaña "Enlazador". lo cual debemos verificar. Las opciones "Crear información de depuración" y "No generar archivo de estado" causan problemas. Normalmente uso un archivo de estado (que promete enlaces incrementales pero crea un archivo en el directorio de compilación con el tamaño de un ejecutable 4). veces o más), lo que a su vez mejora la velocidad de vinculación de proyectos grandes. El uso de RTL dinámico es en sí mismo un tema controvertido y hay muchas discusiones en ambos lados.
Continúa. /Condiciones". Aquí queremos establecer el valor de "Directorios/Condicionales" (ruta de origen de depuración). Siempre debemos configurarlo en $(BCB)sourcevcl, pero si también adjunta otros componentes, normalmente es mejor agregar sus rutas. también (rutas separadas por ";", o puede configurarlas a través del cuadro de diálogo del botón)
La última y más importante configuración está en la pestaña "Paquetes". En todas las experiencias de depuración adecuadas, debe desactivar ". Build with Runtime Package" porque el paquete en sí no contiene ni puede contener información de depuración, lo que puede ser perjudicial para el seguimiento del código VCL estándar. Por ejemplo, cuando desea ver cómo funciona el parámetro x en la función VCL y, pero la mayoría de las veces encontrará que el depurador culpará de la mayoría de los "síntomas" al VCL, aunque la "causa" esté en su código fuente (o en otros componentes (todos hemos estado allí)). lanzó la versión oficial, puede decidir si desea usar el paquete. DLL, compilado sin ejecutar el paquete (estáticamente, permite que su programa se ejecute independientemente de Cbuilder). Deshabilítelo durante la depuración. Presione el botón "Aceptar" y estaremos. listo.
El siguiente cuadro de diálogo sólo necesita abrirse una vez, pero será mejor que reflexionemos aquí sobre la exactitud de la configuración. Bien, abramos "Herramientas | Opciones del depurador".
La opción "Depuración integrada" en la parte inferior del cuadro de diálogo es muy importante. Asegúrate de que esté seleccionado. Presione el botón "Aceptar" para preparar el archivo ejecutable para la compilación. Si ha cambiado su configuración (especialmente después de cambiar el método "Compilar con paquete"), le recomiendo realizar una reconstrucción completa (seleccione Proyecto|Compilar todo). Esto garantizará que todas las unidades del programa se compilen como esperamos.
Establecer puntos de interrupción y dividir en ejecutables
Como cualquier otro depurador que haya visto, C++Builder proporciona potentes capacidades para establecer puntos de interrupción. Básicamente, un punto de interrupción es un punto en el código en el que el programa detiene la ejecución (en lugar de salir, que es solo una pausa en la ejecución) y devuelve el control al depurador. Establecer puntos de interrupción es bastante simple. Simplemente haga clic en el área de la ranura gris a la izquierda de la línea de código del programa que desea configurar y verá aparecer un punto rojo y la línea se volverá roja. En este punto, el programa se detendrá y el control volverá al depurador.
Te preguntarás, ¿qué pasa si no quiero parar cada vez? Por supuesto que puedes, y es fácil de hacer, depende de cuál sea tu criterio para suspender el programa. (Nota del traductor: punto de interrupción condicional). Haga clic derecho en el punto de interrupción (punto rojo) y seleccione Propiedades del punto de interrupción en el menú emergente. Aquí puede configurar los atributos "condición" y "número de pasadas", entre los cuales el atributo "condición" es muy conveniente. Puede ingresar casi cualquier condición con una declaración if(). Sin embargo, recuerde que todas las variables de la condición deben ser visibles en este punto de interrupción. El compilador no compila atributos condicionales en el programa en ejecución, pero en tiempo de ejecución, cuando el programa alcanza un punto de interrupción y se detiene, refleja si se cumple la condición en el punto de interrupción. Si la condición es verdadera, el programa se detiene; en caso contrario, se le permite continuar ejecutándose. Otro atributo, "número de pases", también es fácil de entender. El punto de interrupción se detendrá después de pasar el conteo. La combinación de estas dos propiedades le permite establecer puntos de interrupción muy estrictos al depurar su código.
Otra cosa a tener en cuenta es que cuando encuentre una excepción en el depurador, aparecerá como un punto de interrupción en la línea de código que produjo la excepción. Esto es fácil de crear. ¿Qué medidas debe tomar si ocurre algo inusual? Le mostraré cómo usar rastreos de pila y rastreos para descubrir la causa real de una excepción (es decir, el pequeño fragmento de código que provocó que se lanzara la excepción).
Otro consejo a tener en cuenta al ejecutar su programa es que cualquier línea con un punto azul en el lado izquierdo de la ventana de código se puede establecer como punto de interrupción. Todos los puntos de interrupción ilegales se convertirán en puntos rojos con una pequeña cruz amarilla y las líneas de código se volverán de color canela. Los puntos de interrupción legales se convertirán en puntos rojos con una marca de verificación verde. En tiempo de ejecución, puede establecer/modificar cualquier punto de interrupción y entrará en vigor inmediatamente sin tener que volver a compilar.
Observar el valor almacenado en una variable
¿Qué hacer una vez que el programa se detiene en un punto de interrupción? Una cosa que desea y debe hacer es ver los valores reales de las distintas variables almacenadas en su programa. Esta parte del programa implica mucho, por lo que tendrás que perseverar y soportar el tedio. Afortunadamente, cuando termine de leer este artículo, apreciará mejor la parte más poderosa del depurador. Hay muchas formas de ver el valor de una variable, según lo que desee hacer. Cubriré todos estos métodos, comenzando por observar las variables locales de la función actual.
No hay mucho que decir acerca de observar las variables locales. Simplemente haga clic en "Ver|Depurar Windows|Variables locales" o presione Ctrl-Alt-L, y aparecerá una ventana que muestra las variables locales de la función actual. Las variables en la ventana se actualizarán a medida que baje o suba en el cuerpo de la función.
Uso de la observación
El siguiente paso es configurar la observación de variables para ver las variables en su programa. Como sugiere el nombre, observe las variables y muestre sus valores en la ventana de observación de variables (haga clic en "Ver|Depurar Windows|Relojes" o presione Ctrl-Alt-W).
Hay dos formas de agregar una observación, la primera es seleccionar la variable o expresión que desea observar en la ventana de código (sí...)! Entiende y evalúa la mayoría de las expresiones simples como (i*j)+05 o SomeVector[i].Name), luego haga clic derecho y seleccione "Depurar | Agregar observación en el cursor" o presione Ctrl-f5 Agregar observación. Si es necesario, la ventana de observación se abrirá simultáneamente
También puede agregar un reloj haciendo doble clic en un espacio vacío en la ventana de observación. Esto abrirá el cuadro de diálogo Agregar observación. El campo "expresión" se explica por sí mismo, pero quiero explicar algunos otros campos que también son muy útiles.
El campo "repeticiones" es útil para observar variables de matriz de longitud conocida (como la matriz blah[50]). Puede establecer "Expresión" en el nombre de la matriz (bla en este caso) y "Conteo de repetición" en el número de elementos de la matriz (50 en este caso). Luego muestre cada elemento de la matriz (por ejemplo, bla [0], bla, bla).
"Número de dígitos" se utiliza para establecer el número de decimales para mostrar números decimales de punto flotante. El siguiente conjunto de puntos se utiliza para forzar el tipo de visualización de la variable (sin signo largo en formato hexadecimal). También tenga en cuenta que si hace clic derecho en un punto de observación en la ventana de observación y muestra la opción "Interrumpir al cambiar" en el menú emergente, esto establecerá un punto de interrupción en la variable y pausará el programa cuando la variable cambie.
Usar el Inspector
Inspeccionar variables es la tercera forma de ver datos variables. También es prácticamente la mejor manera de ver los datos completos de la clase. Hay dos formas de comprobar las variables. La primera es hacer doble clic en la variable en la ventana Variables locales, lo que abrirá la ventana del Inspector de depuración que muestra todos los "datos" (variables), "métodos" (funciones) y "datos" (datos) de la variable. "Métodos" (funciones) y "Propiedades". Si son datos simples, se muestra el nombre de la variable y su valor. (¿Y si es una matriz? ¡Eso es genial!)