Practical examples of GDB debugging examples for C/C++
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
btto trace where it came from. - How to inspect arguments with
frameandprint:
(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.
Integrating GDB with modern tooling (2024–2025 trends)
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
gdborlldbunder 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.
Related Topics
Why Your Site Feels Slow (Even When Your Server Is Fast)
Real-world examples of .NET debugger usage in Visual Studio
Real-world examples of Angular CLI debugging examples for 2025
The best examples of Fiddler HTTP debugging examples in real projects
Best examples of Jest debugging examples for JavaScript tests
Real-world examples of debugging React applications with developer tools
Explore More Debugging Frameworks and Tools
Discover more examples and insights in this category.
View All Debugging Frameworks and Tools