Debugging C Programs
C bugs often manifest as crashes, corruption, or silent undefined behavior. A structured debugging toolkit is essential.
GDB Essentials
gcc -g -O0 -std=c17 program.c -o program
gdb ./program
(gdb) break main
(gdb) run arg1 arg2
(gdb) next / step / finish
(gdb) print *ptr
(gdb) watch variable
(gdb) backtrace
(gdb) info locals
AddressSanitizer
gcc -fsanitize=address,undefined -g program.c -o program
./program
# Reports heap-buffer-overflow, use-after-free, etc.
Valgrind
valgrind --leak-check=full --show-leak-kinds=all ./program
# Detects leaks, invalid reads/writes (slower than ASan)
Core Dumps
ulimit -c unlimited
./program # crash
gdb ./program core
(gdb) backtrace
Print Debugging
#ifdef DEBUG
#define DBG(fmt, ...) fprintf(stderr, "%s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#else
#define DBG(fmt, ...) ((void)0)
#endif
Common Pitfalls
- Treating compiler warnings as optional rather than actionable feedback.
- Skipping error checks on library and system calls.
- Copy-pasting examples without adapting to your project’s conventions.
Best Practices
- Enable strict compiler warnings and fix them before merging.
- Write small, testable units with clear input/output contracts.
- Document non-obvious invariants and preconditions.
- Use version control and code review for every change.
Memory and Performance Notes
Debug builds run 10–100× slower with Valgrind. ASan adds ~2× overhead — acceptable for CI.
Exercise
Introduce an off-by-one error in an array loop. Find it with ASan and GDB. Document the fix.
Hint: ASan adds red zones around allocations — out-of-bounds access triggers immediate abort.
Summary
Apply these concepts in small programs before moving to larger projects. Combine with adjacent topics in the learning path for deeper mastery.
Real-World Application
These concepts appear in production codebases — from operating system kernels to embedded firmware. Study open-source projects that use this topic extensively to see idiomatic patterns at scale.
Debugging Checklist
- Reproduce the issue with the smallest possible input.
- Enable compiler warnings and sanitizers.
- Use a debugger to inspect state at the failure point.
- Verify assumptions about types, sizes, and return values.
- Compare working and broken code paths side by side.
- Write a regression test once the bug is fixed.
Further Reading
Consult the ISO C standard, Effective C by Robert C. Seacord, and your compiler documentation for platform-specific behavior.
Quick Reference
Review the code examples on this page before starting the exercise. Type them manually to build muscle memory.
Additional Examples
Consider how this topic applies in a larger project:
// Break the problem into smaller functions
// Test each function independently
// Integrate incrementally
Working through variations of the examples above builds deeper understanding than reading alone.
Interview and Review Questions
- Explain the core concept of this topic in your own words.
- What happens when this code runs with edge-case input (empty, null, zero, max value)?
- How would you debug a bug related to this topic in production?
- What are the performance implications of the approach shown here?
- How does this feature compare to the equivalent in another language you know?
Related Topics in This Path
Review adjacent pages in the learning path before and after this one. Concepts build on each other — skipping ahead often leads to confusion when later pages assume mastery of earlier material.
Return to the section index if you need to fill gaps in prerequisite knowledge.
Tooling Tips
- Enable all compiler or analyzer warnings during development.
- Use version control with small, focused commits for each exercise.
- Pair reading with typing — reproduce every code example by hand.
- Run tests or compile after every change to catch errors early.
- Keep a personal notes file linking concepts to your own project experiences.