GDB, or the GNU Debugger, is a powerful tool for debugging programs written in C and C++. It allows developers to pause program execution, inspect variables, and step through code to identify errors or unexpected behavior. This article presents three diverse examples of using GDB for C/C++ debugging, demonstrating its capabilities and common use cases.
When debugging a C program, you may encounter a situation where a variable’s value does not match your expectations. Using GDB, you can easily inspect and modify these values during execution.
To illustrate this, imagine a simple C program that calculates the factorial of a number. You suspect that a mistake in the loop causes incorrect results.
#include <stdio.h>
int factorial(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
int main() {
int num = 5;
printf("Factorial of %d is %d\n", num, factorial(num));
return 0;
}
After compiling the program with debugging symbols (gcc -g factorial.c -o factorial
), you can start GDB:
gdb ./factorial
Within GDB, set a breakpoint at the factorial
function and run the program:
(gdb) break factorial
(gdb) run
When execution stops at the breakpoint, inspect the variable result
:
(gdb) print result
You can also modify result
if necessary:
(gdb) set variable result = 10
-g
flag to include debugging symbols.continue
to resume execution after inspecting or modifying variables.In this example, we will debug a C++ program where you suspect a logical error is causing incorrect output. The program reads integers from standard input and computes their average.
#include <iostream>
#include <vector>
float computeAverage(const std::vector<int>& numbers) {
float sum = 0;
for (int number : numbers) {
sum += number;
}
return sum / numbers.size();
}
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
std::cout << "Average: " << computeAverage(nums) << std::endl;
return 0;
}
Compile the program with debug information:
g++ -g average.cpp -o average
Start GDB and set a breakpoint before the average calculation:
gdb ./average
(gdb) break computeAverage
(gdb) run
Once execution halts at the breakpoint, use the step
command to go through each line:
(gdb) step
This allows you to verify how sum
and numbers.size()
are being computed line by line.
next
to step over function calls if you wish to skip them.list
command shows the surrounding code, helping you keep track of context.When debugging complex programs, it can be useful to trace the function call hierarchy. This helps identify the source of errors that may originate deep within nested function calls. In this example, we’ll trace a recursive function calculating Fibonacci numbers.
#include <iostream>
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
int num = 5;
std::cout << "Fibonacci of " << num << " is " << fibonacci(num) << std::endl;
return 0;
}
Compile with debugging symbols:
g++ -g fibonacci.cpp -o fibonacci
Run GDB and set a breakpoint in the fibonacci
function:
gdb ./fibonacci
(gdb) break fibonacci
(gdb) run
When the breakpoint is hit, use the backtrace
command to view the call stack:
(gdb) backtrace
This will show you the chain of function calls leading to the current point of execution.
backtrace
command is invaluable for understanding the flow of execution in recursive functions.frame <number>
to switch to a specific stack frame and inspect variables in that context.By leveraging GDB’s capabilities, developers can effectively troubleshoot and debug C/C++ programs, leading to quicker resolution of issues and improved software quality.