¿Cómo depurar código?
Depuración:
- En este apartado, vamos a hablar de cómo puedes depurar tus programas con algunas alternativas. Hablaremos de GDB, el depurador por excelencia para C++ y de algunas interfaces que podemos utilizar para depurar, que utilizan internamente GDB.
GDB:
-
GDB (GNU Debugger) es una herramienta muy usada para depurar programas, especialmente aquellos escritos en C o C++ (aunque también se puede usar con otros lenguajes). Puedes encontrar más información en gdb.
-
GDB te permite:
- Ejecutar tu programa paso a paso para ver cómo se comporta.
- Poner puntos de interrupción (breakpoints) donde quieras pausar la ejecución.
- Inspeccionar variables y ver qué valores tienen en cierto momento.
- Modificar valores en tiempo de ejecución (por ejemplo, cambiar el valor de una variable mientras el programa está pausado).
- Ver la pila de llamadas (call stack) para saber cómo llegaste a cierta función.
- Detectar errores como segmentation faults y ver en qué línea exacta falló el programa.
-
Para poder depurar tu programa, debes compilar con la opción -g, que incluye información de depuración al ejecutable que el depurador utiliza.
- Por ejemplo:
g++ -Wall -g programa.cc -o programa -
Después, en vez de ejecutar el programa como haces normalmente, lo puedes ejecutar utilizndo GDB:
gdb ./programa -
Una vez iniciada la depuración, tienes varias opciones que te pueden ser útiles:
Comando | Descripción |
---|---|
break o b | Pone un breakpoint (por función o por línea) |
break main | Pone un breakpoint en la función main. |
break imprimir | Pone un breakpoint en la función llamada imprimir. |
run o r | Inicia la ejecución del programa |
next o n | Ejecuta la siguiente línea (sin entrar a funciones) |
step o s | Ejecuta la siguiente línea y entra en funciones |
print x | Muestra el valor de la variable x |
backtrace | Muestra la pila de llamadas |
continue | Continúa la ejecución hasta el siguiente breakpoint |
quit o q | Sale de GDB |
- También tienes un modo gráfico para ejecutar GDB, añadiendo el argumento
-tui
. También se puede activar/desactivar pulsandoCtrl + X + A
sin necesidad del argumento-tui
. Puedes encontrar más información en el manual oficial.gdb -tui ./programa
Ejemplo de depuración
- Fíjate en el siguiente ejemplo: tenemos un vector de 3 elementos, y después mostramos en consola con
cout
todos esos elementos.
#include <iostream> using namespace std;
int main() { int numeros[3] = {10, 20, 30};
for (int i = 0; i <= 3; i++) { cout << "Elemento " << i << ": " << numeros[i] << endl; }
return 0; }
-
Al ejecutar este código, nos damos cuenta de que algo sucede, ya que salta el típico error
segmentation fault
. Este error aparece cuando intentamos acceder a una zona de memoria que no está reservada por nuestro programa. -
Vamos a intentar depurar este código paso a paso:
- Compilar con
-g
:
g++ -Wall -g ejemplo.cc -o ejemplo- Abrir GDB en consola indicándole el ejecutable que quieres depurar:
gdb ./ejemplo-
Lo primero que debes hacer es poner un punto de parada para poder controlar el flujo del programa y revisar el estado de las variables. Si no pones un punto de parada, cuando ejecutes el código con la instrucción
run
, se ejecutará tu código y no parará hasta que el programa finalice o hasta que se encuentre con un error. -
Puedes poner un punto de parada al incio del programa:
(gdb) break main -
A continuación, puedes comenzar la ejecución del código, que se parará en el punto de parada que encuentre. En este caso, como hemos puesto uno al inicio del programa, nada más empezar se parará:
(gdb) run -
En el momento que la ejecución esté paralizada, puedes ver el estado de las variables y avanzar línea por línea. Prueba a continuar la ejecución línea a línea hasta que entre en el bucle con la instrucción, tienes dos opciones:
(gdb) next(gdb) step- next - Ejecuta la siguiente línea completa de código, sin meterse en funciones llamadas desde esa línea. Si es una función, la ejecuta y pasa a la siguiente línea.
- step - Ejecuta la siguiente línea, y si es una llamada a función, entra dentro de ella para poder depurar línea por línea dentro de esa función.
-
Una vez en el bucle, puedes ver el estado de la variable i o de incluso de los valores del vector:
(gdb) print i(gdb) print numeros[i] -
También podrías haber puesto un punto de parada en una línea de código correspondiente al bucle. Por ejemplo la línea 8, que es donde se encuentra el cout, y tras poner el punto de parada, ejecutar
run
si todavía no has iniciado la ejecución ocontinue
en caso de que ya esté iniciado:(gdb) break ejemplo.cc:8 # Pone un breakpoint en la línea 8(gdb) run (o continue) -
Si necesitas probar una línea de código que no se encuentra en tu programa, puedes ejecutarlo con la instrucción
call
, que ejecuta lo que le indiques como si estuviese en el propio programa que se está depurando:(gdb) call std::cout << "Hola desde GDB" << std::endl -
Si ejecutas paso a paso con
next
ostep
, encontrarás que en un momento dado salta un error en el bucle. Fíjate en el valor de la variablei
en todo momento y compárala con el tamaño del vector. -
Y cuando termines, no olvides salir de GDB con:
(gdb) quit
- Compilar con
Depurar con un interfaz
- Como imaginarás, muchos interfaces de desarrollo integrado (IDEs) incluyen una manera de depurar más gráfico que el modo consola de GDB. Se puede depurar de la misma manera, pero podría considerarse más intuitivo depurar utilizando un editor de texto. Vamos a ver un par de ejemplos:
Depurar con Visual Studio Code
- Para poder depurar en Visual Studio Code (VSCode), tienes que tener instalada la extensión correspondiente para
C++
.
Figura 1: Extensiones de C++ útiles para depurar. La primera es la importante.
-
Una vez instalada la extensión, debemos de abrir la carpeta donde se encuentra el código. Si abres únicamente el fichero, no te funcionará la depuración. Debes abrir la carpeta. Esto lo puedes hacer en Archivo/Abrir carpeta, y seleccionas la carpeta que contiene tu proyecto.
-
Después, si es la primera vez que depuramos nuestro proyecto, debemos configurar la depuración. Para ello, necesitamos el fichero
tasks.json
, que VSCode lo genera automáticamente por defecto al comenzar la primera depuración. Para ello, primero abre el fichero donde se encuentra tu código y pulsaF5
. Elige la opción de compilar y depurar cong++
. Cuidado: no seleccionesgcc
(compilador de C, no de C++).
Figura 2: Boton de depuración (Tecla F5).
- Si te fijas, se habrá creado una carpeta oculta llamada
.vscode
. En ese directorio VSCode guardará algunos archivos de configuración, incluido eltasks.json
que comentábamos antes. Ese fichero concretamente nos permitirá configurar la forma de configurar nuestro código.
Figura 3: Creación del fichero de configuración de tu proyecto tasks.json.
- El contenido del fichero
tasks.json
será similar a este:
{ "tasks": [ { "type": "cppbuild", "label": "C/C++: g++ compilar archivo activo", "command": "/usr/bin/g++", "args": [ "-fdiagnostics-color=always", "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}" ], "options": { "cwd": "${fileDirname}" }, "problemMatcher": [ "$gcc" ], "group": { "kind": "build", "isDefault": true }, "detail": "Tarea generada por el depurador." } ], "version": "2.0.0"}
- De todo el fichero tasks.json, nos vamos a centrar en la parte que configura la forma de compilar, en el apartado
args:
. No tienes que borrar nada del fichero, solo vamos a centrar la explicación en una parte de su contenido, que es la que nos interesa.
{ "args": [ "-fdiagnostics-color=always", "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}" ],}
-
Estos son los argumentos que se añadirán al comando del compilador para compilar tu código. Fíjate que puedes encontrar el argumento
-g
, que decíamos antes que es necesario para poder depurar el programa después de compilarlo. Justo después vendría el fichero (o los ficheros) que quieres compilar. Concretamente pone lo siguiente:"${file}"
. Este argumento se refiere al fichero que tienes seleccionado en el momento de compilar y depurar tu código (con F5). Por defecto, si solo tienes un único fichero de código, bastaría con abrir tu fichero en VSCode y pulsar F5. Con esto compilaría tu código y comenzaría en modo depuración.- Sin embargo, si tienes más de un fichero de código que deben ser compilados, deberías modificar esta línea. En las prácticas, tendrás un fichero
main.cc
, además de otros ficheros que necesitarás compilar. Si no modificastasks.json
, VSCode solo tratará de compilar el fichero abierto en ese momento, y por lo tanto no funcionará. En este caso, tienes dos opciones:- Opción 1: Añadir manualmente todos los ficheros
.cc
a compilar. Por ejemplo:{"args": ["-fdiagnostics-color=always","-g","mainp1.cc", "p1.cc", //Aquí pones la lista de ficheros .cc"-o","${fileDirname}/${fileBasenameNoExtension}" //Aquí puedes fijar el nombre del ejecutable que se genera],} - Opción 2: Si tienes todo tu código en la misma carpeta y solo tienes un único main en todo el código, puedes decirle a VSCode que utilice todos los ficheros con extensión
.cc
, poniendo"*.cc"
en los argumentos del compilador. Quedaría así:{"args": ["-fdiagnostics-color=always","-g","*.cc","-o","${fileDirname}/${fileBasenameNoExtension}"],}
- Opción 1: Añadir manualmente todos los ficheros
- Sin embargo, si tienes más de un fichero de código que deben ser compilados, deberías modificar esta línea. En las prácticas, tendrás un fichero
-
Si te fijas, también está el argumento
-o
, seguido por otro argumento que representaría el nombre del ejecutable que se genera. Por defecto, el fichero tendrá configurado lo siguiente:"${fileDirname}/${fileBasenameNoExtension}"
, que indica que se generará un ejecutable con el mismo nombre que el fichero de código en el que has pulsado la tecla F5 para depurar. Si tienes abierto tu ficheromain.cc
, se creará un ejecutable llamadomain
. Si quieres, puedes cambiarlo por el nombre que te parezca más conveniente. -
Una vez configurado todo lo anterior, ya podrás depurar tu código. Asegúrate de que solo tengas una función
main
para que esto funcione. Deberás poner breakpoints en el código para que se pare el flujo del programa y puedas revisar el estado de las variables y ejecutar paso a paso, similar a cómo lo hacemos conGDB
. Recuerda que VSCode internamente utilizaGDB
.
Figura 4: Ejemplo de breakpoint y depuración.
- Mientras estás depurando, encontrarás algunas opciones en la parte superior de VSCode, con las que podrás controlar el flujo del programa, similar a cómo funcionaba
GDB
. En la figura siguiente verás para que sirve cada botón. Se han mantenido los términos en inglés de los comandos vistos anteriormente paraGDB
. Funcionan exactamente igual.
Figura 5: Opciones de depuración para controlar el flujo del programa.
NOTA: la configuración mencionada previamente se ha hecho en Linux. Si tienes otro sistema operativo, es posible que algún paso cambie. Se recomenda que utilices el sistema operativo del laboratorio de la EPS para evitarte problemas.
Depurar con Data Display Debugger (DDD)
-
Otra alternativa es utilizar DDD, que lo tienes instalado en el laboratorio de la EPS. Puedes consultar la web de DDD para más información. Se trata de un interfaz gráfico para depurar. La forma de trabajar con él es:
- Se compila manualmente el programa con el argumento
-g
. - Se abre DDD y se carga el ejecutable generado.
- Se añaden breakpoints y se depura, parecido a VSCode.
- Se compila manualmente el programa con el argumento
-
Para utilizarlo en tu ordenador personal, debes instalarlo. Si tienes una versión de Linux como la del laboratorio de la EPS, puedes instalarlo así:
apt-get install ddd- Si te pide permisos de superusuario, tendrás que añadir la palabra
sudo
antes de esa instrucción, para indicar al terminal que quieres ejecutarlo como superusuario. En este caso, te pedirá la contraseña de tu usuario.
- Si te pide permisos de superusuario, tendrás que añadir la palabra
-
Al iniciarlo, verás que tiene un interfaz bastante sencillo.
Figura 6: Interfaz gráfico de DDD.
- Si entras en el menú
File
, verás que tienes varias opciones para cargar tu programa. Entra enOpen program
y selecciona tu programa generado tras compilarlo con-g
previamente.
Figura 7: Cargar tu programa en DDD.
- Una vez cargado, podrás incluir breakpoints y controlar el flujo del programa. Verás que tienes muchas opciones a la hora de controlar el flujo. Entre las opciones, aparece
Run
,Next
yStep
, que ya las conoces. Tienes más opciones, pero en la práctica, son probablemente las que más vayas a utilizar.
Figura 8: Ejemplo de depuración en DDD.
- Fíjate que en la parte inferior tienes una consola donde puedes escribir comandos de GDB.
Consejos.
-
No tengas miedo de repetir la depuración las veces que haga falta para intentar detectar el error. Es posible, que si depuras el código, no localices exactamente dónde está el error, pero pueda darte una pista de dónde podría estar. En este caso, es recomendable repetir la depuración centrándote en la parte sospechosa de código. Lo importante es que en cada depuración que hagas, tu objetivo sea intentar acotar la zona del código sospechosa, en la que dedicar la mayor parte de tu esfuerzo a la hora de detectar y corregir el error.
-
Para poder depurar correctamente, tienes que tener claro algo importante: debes saber a ciencia cierta qué es lo que esperabas obtener con tu código para poder compararlo con lo que realmente estás obteniendo. Si no tienes claro lo que deberías obtener, que puede ser porque no entiendes bien tu programa o porque no entiendes algo de la especificación, te va a ser muy difícil detectar errores de ejecución.
-
Hay más alternativas para depurar código en C++. Un ejemplo es Nemiver, que puedes instalarlo en tu ordenador personal, y funciona similar a DDD. Eso sí, recuerda que si utilizas una herramienta que no está en el laboratorio de la EPS, cuando vayas a enfrentarte al control de prácticas, no tendrás dicha herramienta, por lo que lo recomendable sería utilizar las herramientas de las que dispones en el laboratorio.