Capítulo 4b.- El debugador. Aunque un programa este sintácticamente bien escrito, es decir, el compilar lo comprende y es capaz de construir un binario a partir de él, puede ocurrir que el código no funcione bien, por ejemplo por una de las siguientes razones: 1. Los resultados que devuelve no están bien. 2. El programa responde erráticamente a nuestras órdenes. 3. El programa se vuelve inestable. 4. El programa devuelve un error. En Linux, estos errores (mas conocidos como excepciones) se notifican mediante un fallo de segmentación o segmentation fault. Hasta el momento nuestra única forma de descubrir donde estaba el error era repasando una y otra vez el código, y llenándolo de printf hasta dar con el problema. En este capítulo, o más bien subcapítulo, vamos a aprender a emplear una herramienta que en el modo terminal casi compite con los printf, pero que con una IDE (Code::Blocks) se vuelve una herramienta indispensable. El debugador. El debugador que vamos a emplear es una de las componentes de nuestro compilador, gcc, y se llama gdb. gcc = (GNU C compiler). gdb = (GNU DeBugger). Este depurador (nombre castellano) permite no solo trazar el código (hacer un seguimiento durante su ejecución), sino que permite incluso modificar el valor de las variables. Nosotros no lo vamos a estudiar en modo terminal, sino que acudiremos directamente a Code::Blocks. Usando el debugador en Code::Blocks. Como nosotros usamos el compilador gcc, Code::Blocks ya sabe que debe usar gdb, no obstante podéis acudir al Anexo I para aprender a crear un nuevo compilador en Code::Blocks, y configurarlo. Así pues, tan sólo necesitamos un código, empezemos con el siguiente código: /home/ VUESTRO USUARIO /proyectos_codeblocks/tallerprogramacion/004b/debug-001 1.#include <stdio.h> 2.#include <stdlib.h> 3.int main() 4.{ 5. int a, b, c; 6. printf("dame un numero:\n"); 7. scanf("%d",&a); 8. printf("dame otro:\n"); Taller de programación C/C++ 1
9. scanf("%d",&b); 10. c = a + b; 11. printf("su suma es: %d\n", c); 12. return 0; 13.} Sencillo código que suma dos números. Lo primero que vamos a hacer es meter la pata, y compilar la versión Release. Recordemos que la versión Release está optimizada, es decir, es más rápida, aunque a cambio se pierde la posibilidad de debugar. Para ello vamos a elegir el target Release. Y compilamos. Eligiendo Release como objetivo. Y una vez hecho esto, vamos a insertar un breakpoint. Los breakpoints son líneas en las que el debugador se detiene antes de ejecutarlas a la espera de órdenes. Los breakpoints sólo pueden ponerse en líneas que requieran algún cálculo, es decir, si se colocan en la declaración de variables enteras, por ejemplo, no serán válidos y se pasarán por alto, o se moverán a la siguiente línea. Por supuesto no se puede insertar breakpoints en líneas vacías. Para ello nos ponemos por ejemplo en el primer printf, y damos a Debug/Toggle breakpoint, o simplemente apretamos F5. Veréis que aparece un punto rojo a la izquierda de la línea: Breakpoint en la línea 6. Y ahora iniciamos el debugado pulsando F8. El debugado continuará hasta que encuentre un breakpoint, o una excepción. Es conveniente para debugar conocer los atajos por teclado, que los encontrareis en el menu Debug. Como podéis comprobar, al estar en la versión Release, el código se ejecuta con toda normalidad, sin detenerse en el breakpoint. Es decir, hay que tener cuidado cuando se debuga, de que efectivamente podamos hacerlo. Ahora, sin tocar nada, elegimos el objetivo Debug en lugar del Release. Y compilamos y Taller de programación C/C++ 2
apretamos F8. Veréis como el código comienza su ejecución normal, pero repentinamente cambia de plano, y volvemos a Code::Blocks, donde vemos un cursor amarillo encima del punto rojo del breakpoint. Estado del código. Ese cursor indica donde esta el código en este instante. Si ahora apretamos F7 saltaremos a la línea siguiente de este mismo archivo, y entonces se ejecutará el printf. Efectivamente si volvemos a la terminal veremos que a aparecido nuestro cartel. Los printf, a pesar de ejecutarse en ese instante, sólo aparecerán por pantalla si hay un \n (retorno de carro), si no, se almacenarán en el buffer a la espera de que, o aparezca un retorno de carro, o se llene el buffer, o se fuerce su vaciado. Si ahora volvemos a Code::Blocks, y volvemos a saltar otra línea con F7, veremos que desaparece el cursor amarillo, pues tendremos que volver a la terminal a meter un número, ya que la orden era un scanf. Asi que volvemos a la terminal, e introducimos un 5. Inmediatamente nos devuelve a Code::Blocks, donde hemos recuperado nuestro cursor. Abajo a la derecha tenemos una ventana titulada Watches. Ventana de seguimiento de variables. Si no la tenéis, podéis activarla y desactivarla desde Debug/Debugging windows/watches. En esa ventana, si desplegáis Local variables, podréis ver las variables declaradas dentro de la función en la que os encontréis (en este caso main()). En este caso hay tres variables declaradas: a: Entero que vale 5, pues se lo insertamos en la línea anterior. b: Entero con un valor aleatorio, pues aún no le hemos dado ninguno. c: Entero con un valor aleatorio, pues aún no le hemos dado ninguno. Si desplegáis Function Arguments, podremos ver las variables que recibió nuestra función. Como en este caso main no recibió ningún argumento, encontraremos una advertencia de ello. Adicionalmente, podemos insertar nuestras propias trazas, dando botón derecho y diciendo Add Watch. En la ventana que nos sale tendremos muchas más operaciones disponibles. Incluso podemos añadir operadores o funciones. Por ejemplo añadiremos una watch llamada a+b. El resto lo dejaremos estar. Vemos como efectivamente nos devuelve el valor de ambos sumados (pese a la incoherencia de b). Demos dos veces F7, e introduzcamos en la terminal, por ejemplo, un 3. Vemos que nuestras watches b, y a+b, se han puesto rojas, y eso es debido a que su valor se ha modificado en el último paso. Un paso puede contener muchas órdenes, un paso sólo es lo acaecido desde la última parada Taller de programación C/C++ 3
del debugador. Efectivamente vemos como todos los valores son correctos, y solo falta por asignar c. También podemos ver el valor de las variables, y alguna información adicional, si mantenemos el cursor del ratón sobre ellas en el código (da igual que sea en su declaración, en una operación,...). Ahora, para terminar, vamos a decirle al debugador que continúe hasta la próxima parada (breakpoint o excepción) pulsando Ctrl+F7. El programa termina y se cierra inmediatamente, saliendo del debugador subitamente. hay que tener cuidado, pues en el modo de debugado el programa no dirá el famoso Press ENTER to continue. Bueno, ya hemos visto como podemos ir navegando por el código, inspeccionando las variables, con lo que podremos analizar si alguna operación no está siendo correcta. Cambiemos ahora ligeramente el código como sigue: 1.#include <stdio.h> 2.#include <stdlib.h> 3.int main() 4.{ 5. int a, b, c; 6. int suma(int a,int b); 7. printf("dame un numero:\n"); 8. scanf("%d",&a); 9. printf("dame otro:\n"); 10. scanf("%d",&b); 11. c = suma(a,b); 12. printf("su suma es: %d\n", c); 13. return 0; 14.} 15.int suma(int a,int b) 16.{ 17. return a+b; 18.} Retiremos todos los breakpoints, e insertemos uno en la línea 11. Compilamos, e inciamos el debugado con F8. Insertemos de nuevo 5 y 8. El debugador se detiene en la línea 11, y entonces apretamos F7, pues suponemos (y hacemos bien), que la función suma cumple su objetivo correctamente. Efectivamente al pasar por esa línea, e inspeccionar c, vemos que todo es correcto. Pero supongamos que descubrimos que c no es correcto, entonces podríamos entrar a analizar la función suma, para ello presionamos Debug/Stop debugger, y volvemos a empezar el debugado con F8. Una vez en la línea 11, apretamos Shift+F7, y vemos como el cursor va inmediatamente a la función suma. Vemos que Local variables queda vacía (pues en suma no se declara nada), y que en cambio aparecen a y b como argumentos. Si tratamos de inspeccionar c, nos dará un error pues c no existe en suma (hacerlo). Podemos debugar igual que hacíamos en main, y podemos salir de suma (ejecutando todo lo que la falte por hacer) presionando Ctrl+Shift+F7. Si os fijáis, el cursor no salta a la línea 12, eso es porque a pesar de haber ejecutado suma, aún necesita asignársela a c. Taller de programación C/C++ 4
Solo podréis entrar en aquellas funciones de las que dispongais del archivo fuente (*.c, *.cpp, etc...) en vuestro proyecto. Ya sabemos debugar en líneas generales. Ahora vamos a buscar una excepción. Para ello cambiamos el código así: 1.#include <stdio.h> 2.#include <stdlib.h> 3.int main() 4.{ 5. int a, b, c; 6. int suma(int a,int b); 7. printf("dame un numero:\n"); 8. scanf("%d",&a); 9. printf("dame otro:\n"); 10. scanf("%d",b); 11. c = suma(a,b); 12. printf("su suma es: %d\n", c); 13. return 0; 14.} 15.int suma(int a,int b) 16.{ 17. return a+b; 18.} Donde hemos cambiado el segundo scanf. En el siguiente capítulo veremos porque eso es un error, pero de momento nos conformamos con probarlo. Quitamos todos los breakpoints, compilamos e iniciamos el debugado con F8. Volvemos a introducir 5 y 3, y vemos como nos devuelve a Code::Blocks, y abajo a la derecha nos muestra una notificación de que el programa le ha enviado un fallo de segmentación. El cursor señala la línea donde se encontró el fallo, la línea 10, y además nos aparece una nueva ventana llamada Call Stack, donde vemos las funciones a las que se ha ido llamando. Si no os aparece podéis hacer Debug/Debugging windows/call Stack. Anexos Anexo I: Configurar un compilador en Code::Blocks. Code::Blocks trae una larga lista de compiladores preconfigurados, que podemos encontrar en Settings/Compiler and debugger... Selector de compilador. Nosotros vamos a añadir el soporte para fortran. Para ello lo primero que debemos hacer es instalar el compilador de Fortran, que en GNU es gfortran, lo que se hace desde una terminal escribiendo (instalaremos también algunos extras): Taller de programación C/C++ 5
$> sudo apt-get install gfortran gfortran-multilib libgfortran3-dbg Una vez tenemos el compilador instalado, solo hay que enseñarle a Code::Blocks como se emplea. Para ello cogemos el compilador GNU GCC Compiler (El que viene por defecto), y le damos a Copy. Copiando el compilador. Y lo llamamos GNU Gfortran Compiler. El programa nos advertirá sobre la necesidad de configurarlo. Pues vamos a configurarlo, para ello el primer paso es decirle cuales son las nuevas herramientas a utilizar (evidentemente ya no nos interesa que trate de compilar con gcc, pues es otro lenguaje), para ello seleccionamos nuestro nuevo compilador (si no lo esta ya), y vamos a la pestaña Toolchain executables. Y abajo del todo veréis algo como esto: Ejecutables del compilador. Por norma general, cuando se añade un compilador nuevo, hay que tratar de usar uno que se le parezca lo más posible, de esta forma la mayoria de las opciones serán similares. Bien, comencemos a configurarlo. Como se puede ver Code::Blocks es una IDE claramente orientada a C/C++, pero eso no es un impedimento, ya que cuando la extensión del archivo no coincida, tratará de emplear el del lenguaje C. Sea como sea, nosotros debemos emplear gfortran para cualquier extensión de archivo que implique este compilador, así que los dos primeros campos deberán poner gfortran. Nosotros todavía no hemos entrado en el mundo de las librerías, pero aún así podemos adelantar que las librerías dinámicas son algo parecido a los *.cpp ya compilados, y por tanto se deben linkar con el propio compilador. En el caso de C/C++, se emplea el compilador de C++, ya que es más completo (C++ contiene a todo C), pero en nuestro caso no tenemos que distinguir, rellenaremos este campo con gfortran. Las librerías estáticas son otro cantar, pero una de las ventajas de haber copiado todo de otro compilador semejante es que este tipo de cosas no varían, así pues dejamos este campo como está. El depurador gdb funciona también para fortran, así que lo dejamos también. Taller de programación C/C++ 6
Las resources las dejamos de momento, no toquéis este campo. El make también lo vamos dejando, no toquéis tampoco este campo. Bien, Code::Blocks ya sabe cuales son los ejecutables que debe emplear, ahora tan sólo debemos asegurarnos de que en la pestaña Compiler settings estén todas las opciones vacías, para que ninguna pueda provocar algún conflicto. En el caso de gfortran no ocurre nada, pues al ser de la misma familia que gcc, las opciones usan la misma semántica (-w quiere decir lo mismo para ambos), pero si empleáramos un compilador, de intel por ejemplo, las opciones no seguirían exactamente la misma semántica. En este último caso es preferible configurar las opciones manualmente en Other options. Ahora podemos crear un nuevo proyecto con nuestro compilador, y podremos crear un programa fortran. Ejercicios Huelga hacer ejercicios de este capítulo. El debugador es algo que se aprende simplemente programando. Enlaces http://gcc.gnu.org/ http://www.codeblocks.org/ Taller de programación C/C++ 7