GDB Debugging Examples for C/C++

Explore practical examples of using GDB for effective debugging in C and C++ programming.
By Jamie

Introduction to GDB

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.

Example 1: Inspecting Variable Values During Runtime

Context

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

Notes

  • Ensure you compile with the -g flag to include debugging symbols.
  • Use continue to resume execution after inspecting or modifying variables.

Example 2: Step Through Code to Identify Logic Errors

Context

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.

Notes

  • Use next to step over function calls if you wish to skip them.
  • The list command shows the surrounding code, helping you keep track of context.

Example 3: Trace Function Calls with Backtrace

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.

Notes

  • The backtrace command is invaluable for understanding the flow of execution in recursive functions.
  • You can use 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.