Practical examples of GDB debugging examples for C/C++

If you write C or C++ for a living, you eventually hit a bug that logging just can’t explain. That’s where good, practical examples of GDB debugging examples for C/C++ make the difference between staring at a frozen terminal and actually understanding what your code is doing. Instead of another dry reference manual, this guide walks through real examples, the kind you actually hit in day‑to‑day work: segmentation faults, memory corruption, mysterious hangs, and heisenbugs that disappear as soon as you print something. GDB hasn’t disappeared just because fancy IDEs are more popular in 2024. Under the hood, those tools still depend on the same debugging engine. Learning from concrete examples of how to drive GDB directly gives you more control, better performance insight, and a deeper understanding of what the compiler and runtime are doing with your code. Let’s walk through realistic sessions you can copy, tweak, and actually use in your own projects.
Written by
Jamie
Published
Updated

Modern C/C++ development leans heavily on IDEs like VS Code, CLion, and Visual Studio. But when you’re SSH’d into a production‑like Linux box, trying to understand a crash dump at 2 a.m., you’re back to GDB. The best examples of GDB debugging examples for C/C++ are the ones that match that reality:

  • You have only a binary and maybe core dumps.
  • You need to inspect optimized builds.
  • You’re chasing race conditions and memory issues that logging can’t safely expose.

GDB is still the reference debugger for GCC and Clang on Linux, and it’s the tool most higher‑level debuggers wrap. The GNU Project maintains the official manual at gnu.org, and it’s still updated regularly.

Below, each section focuses on a concrete, self‑contained code sample plus the exact GDB commands you’d type. These are real examples you can paste into a file, compile, and step through.


Simple crash: examples of GDB debugging examples for C/C++ with segfaults

Start with the classic: a segmentation fault from a bad pointer. This is the most common example of GDB usage you’ll see in tutorials, but we’ll make it realistic and slightly broken in several ways.

// file: segfault.c
#include <stdio.h>

void print_message(char *msg) {
    printf("Message: %s\n", msg);
}

int main(void) {
    char *p = NULL;
    print_message(p);   // Oops: passing NULL to printf("%s")
    return 0;
}

Compile with debug info:

gcc -g -O0 segfault.c -o segfault
./segfault   # will likely crash

Now run it in GDB:

gdb ./segfault
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
(gdb) bt

The bt (backtrace) command shows the call stack, pointing to print_message and then main. This is one of the cleanest examples of GDB debugging examples for C/C++ because it teaches you:

  • How to reproduce a crash under GDB.
  • How to use bt to trace where it came from.
  • How to inspect arguments with frame and print:
(gdb) frame 0
(gdb) print msg
$1 = 0x0

You immediately see msg is NULL, which explains the segfault with %s.


Off‑by‑one and memory corruption: a deeper example of GDB usage

Segfaults are easy. Silent memory corruption is not. Let’s look at one of the best examples of GDB debugging examples for C/C++ when you suspect a buffer overrun.

// file: overflow.c
#include <stdio.h>
#include <string.h>

int main(void) {
    char buf[8];
    char *secret = "OK";

    strcpy(buf, "ABCDEFGHIJK");  // Intentionally too long

    printf("buf = %s\n", buf);
    printf("secret = %s\n", secret);
    return 0;
}

Compile with debug symbols and no optimization for easier stepping:

gcc -g -O0 overflow.c -o overflow

Run in GDB:

gdb ./overflow
(gdb) break main
(gdb) run
(gdb) next     # step over line by line
(gdb) print buf
(gdb) print secret

You can use x/16bx buf to inspect raw bytes in memory:

(gdb) x/16bx buf

This shows you how strcpy writes past the buf boundary and may overwrite secret. Combine this with tools like AddressSanitizer from LLVM (clang.llvm.org/docs/AddressSanitizer.html) and you get one of the more powerful real examples of GDB debugging examples for C/C++: compile with -fsanitize=address -g, run under GDB, and use both the sanitizer’s report and GDB’s stack/heap inspection.


Stepping into functions and inspecting state: real examples with recursion

Another practical example of GDB debugging examples for C/C++ is tracing logic bugs in recursive functions. Consider a broken factorial implementation:

// file: factorial.c
#include <stdio.h>

long fact(int n) {
    if (n == 0)
        return 0;   // Bug: should be 1
    return n * fact(n - 1);
}

int main(void) {
    int n = 5;
    long result = fact(n);
    printf("fact(%d) = %ld\n", n, result);
    return 0;
}

Compile:

gcc -g -O0 factorial.c -o factorial

Debug session:

gdb ./factorial
(gdb) break fact
(gdb) run
(gdb) info args
(gdb) info locals
(gdb) step       # step into recursive call
(gdb) bt         # see recursive stack frames
(gdb) frame 1    # switch between frames
(gdb) print n

By watching n and the return values, you quickly see that fact(0) returns 0 instead of 1. This is a good example of GDB debugging examples for C/C++ logic issues where the program doesn’t crash but returns wrong results.


Debugging optimized builds: examples include inlining and missing variables

Real production bugs tend to show up in optimized binaries, not in -O0 debug builds. That’s where GDB gets trickier. Here’s a compact example of GDB debugging examples for C/C++ in optimized mode.

// file: optimize.c
#include <stdio.h>

static int add(int a, int b) {
    int tmp = a + b;
    return tmp;
}

int main(void) {
    int x = 10;
    int y = 20;
    int z = add(x, y);
    printf("z = %d\n", z);
    return 0;
}

Compile with optimization but keep debug info:

gcc -g -O2 optimize.c -o optimize

In GDB:

gdb ./optimize
(gdb) break main
(gdb) run
(gdb) next
(gdb) step      # try to step into add

You may notice GDB skips straight past add because it was inlined, or some local variables show as <optimized out>. This is one of the more realistic examples of GDB debugging examples for C/C++ in 2024: learning to read optimized code, use disassembly, and rely more on bt, info registers, and disassemble /m when high‑level symbols are missing.


Concurrency: race conditions and watchpoints as a best example

Multi‑threaded bugs are where GDB starts to feel like a power tool instead of a chore. Consider this intentionally racy example using POSIX threads:

// file: race.c
#include <pthread.h>
#include <stdio.h>

volatile int counter = 0;

void* worker(void *arg) {
    for (int i = 0; i < 1000000; ++i) {
        counter++;   // Data race
    }
    return NULL;
}

int main(void) {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, worker, NULL);
    pthread_create(&t2, NULL, worker, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("counter = %d\n", counter);
    return 0;
}

Compile with -g -O0 -pthread:

gcc -g -O0 -pthread race.c -o race

In GDB, you can use watchpoints and thread commands:

gdb ./race
(gdb) break worker
(gdb) run
(gdb) info threads
(gdb) thread 2
(gdb) watch counter
(gdb) continue

Each time counter changes, GDB stops and shows which thread did it. This is one of the best examples of GDB debugging examples for C/C++ concurrency issues: using info threads, thread, bt in each thread, and watch to track shared state. For more theory on race conditions and safe concurrency patterns, the Carnegie Mellon 15‑213 materials at cmu.edu are worth a read.


Attaching to a running process: real examples from production‑like setups

Sometimes you can’t restart the program with gdb ./prog. Maybe it’s a long‑running service. A practical example of GDB debugging examples for C/C++ in that situation is attaching to a PID.

Imagine a simple HTTP‑like server:

// file: server.c
#include <stdio.h>
#include <unistd.h>

int main(void) {
    int counter = 0;
    while (1) {
        printf("Handling request %d\n", counter++);
        sleep(1);
    }
    return 0;
}

Compile and run:

gcc -g -O0 server.c -o server
./server &

Find its PID with ps and then:

gdb -p <PID>
(gdb) bt
(gdb) info locals
(gdb) break main
(gdb) continue

You can inspect counter, set new breakpoints, or even change variables on the fly with set variable counter = 1000. In many real examples of GDB debugging examples for C/C++, this attach workflow is how engineers debug production‑like services without restarting them.


Core dumps: examples include post‑mortem debugging

In production you might only have a core dump, not a running process. That’s another classic example of GDB debugging examples for C/C++.

Enable core dumps on Linux:

ulimit -c unlimited
./segfault   # from earlier

After the crash, you’ll see a core file (or similar name depending on your system). Then:

gdb ./segfault core
(gdb) bt
(gdb) frame 0
(gdb) info locals

This is post‑mortem debugging: you can’t continue execution, but you can inspect the state at the moment of failure. The GNU GDB manual at sourceware.org has more detail on how different systems name and configure core dumps.


In 2024 and 2025, many teams don’t run GDB directly every day, but they still benefit from it indirectly. A few real examples:

  • VS Code’s C/C++ extension uses gdb or lldb under the hood; the launch.json config just wraps GDB commands.
  • CI systems capture core dumps and then run automated GDB scripts to extract stack traces.
  • Sanitizers (AddressSanitizer, ThreadSanitizer, UBSan) are often used together with GDB to get both high‑level diagnostics and low‑level inspection.

Learning these examples of GDB debugging examples for C/C++ pays off because the same mental model applies whether you’re clicking “Debug” in an IDE or typing run in a terminal.

For a deeper background on debugging strategies and program behavior, MIT’s open courseware on systems programming and C at mit.edu remains a solid reference.


FAQ: common questions about GDB and real debugging examples

Q: What are some practical examples of GDB debugging examples for C/C++ I should practice first?
Start with a simple segfault example, a buffer overflow that corrupts nearby variables, a recursive logic bug like the factorial function, and a basic thread race with pthread. Those four give you a good feel for run, bt, print, step, next, watch, and info threads.

Q: Can you give an example of using GDB to debug a memory leak?
GDB by itself is not great at finding leaks, but it pairs well with tools like Valgrind or AddressSanitizer. Run your program with -fsanitize=address -g, reproduce the leak or invalid free, then start GDB on the same binary. Use the sanitizer’s stack trace as a guide and reproduce under GDB, setting breakpoints on the reported allocation or free sites.

Q: Are these examples of GDB debugging examples for C/C++ different on macOS or Windows?
On macOS, LLDB is more common, but the workflow is similar. On Windows, you’re more likely to use Visual Studio’s debugger, but if you’re in a POSIX‑like environment (WSL, Cygwin, MSYS2), GDB behaves much like on Linux. Command names and core dump handling differ, but the examples include the same ideas: run, break, backtrace, inspect, and step.

Q: How do I debug optimized code when variables show as <optimized out>?
Recompile with -g -Og instead of -O2 when possible; -Og is designed to keep debugging reasonable while still optimizing. If you must debug full -O2 or -O3, rely more on disassembly (disassemble /m), registers (info registers), and breakpoints on functions or addresses instead of line numbers.

Q: Where can I learn more advanced GDB usage beyond these real examples?
The official GDB manual at sourceware.org is still the primary reference. For broader debugging and systems background, open courseware from universities such as MIT and CMU, and documentation from the LLVM project, provide high‑quality material that lines up well with the kinds of examples of GDB debugging examples for C/C++ shown here.

Explore More Debugging Frameworks and Tools

Discover more examples and insights in this category.

View All Debugging Frameworks and Tools