Module 33: Debugging in C++
Introduction
Debugging is the process of finding, analysing, and fixing bugs in a program. Every software developer spends a significant amount of time debugging because even small mistakes can lead to crashes, incorrect output, memory leaks, or undefined behaviour.
Modern C++ provides several debugging tools, including:
- GDB (GNU Debugger)
- Visual Studio Debugger
- VS Code Debugger
- Assertions
- Logging
- Debug Symbols
- Stack Traces
- Breakpoints
- Watch Variables
This module explains how to debug C++ programs effectively using practical examples.
Learning Objectives
After completing this module, you will understand:
- What is debugging?
- Compiling with debug information
- GDB basics
- Breakpoints
- Stepping through code
- Inspecting variables
- Assertions
- Logging
- Visual Studio Debugger
- VS Code Debugger
- Best debugging practices
What is Debugging?
Debugging is the process of locating and correcting software defects.
1Write Code 2 │ 3 ▼ 4Compile 5 │ 6 ▼ 7Run Program 8 │ 9 ▼ 10Bug Found 11 │ 12 ▼ 13Debug 14 │ 15 ▼ 16Fix Bug
Types of Bugs
| Bug Type | Example |
|---|---|
| Syntax Error | Missing ; |
| Runtime Error | Divide by zero |
| Logical Error | Wrong algorithm |
| Memory Error | Accessing freed memory |
| Segmentation Fault | Invalid pointer access |
Compile with Debug Symbols
GDB requires debugging information.
1g++ -g main.cpp -o app
Optimisation is usually disabled while debugging.
1g++ -g -O0 main.cpp -o app
Example Program
1#include <iostream> 2 3using namespace std; 4 5int divide(int a, int b) 6{ 7 return a / b; 8} 9 10int main() 11{ 12 int x = 20; 13 int y = 0; 14 15 cout << divide(x, y); 16 17 return 0; 18}
Running this program causes a runtime error (division by zero).
GDB (GNU Debugger)
Start GDB
1gdb ./app
Run Program
1(gdb) run
Set a Breakpoint
1(gdb) break main
or
1(gdb) b main
Start Debugging
1(gdb) run
Output
1Breakpoint 1, main()
View Source Code
1(gdb) list
Example
11 int main() 22 { 33 int x = 20; 44 int y = 0; 55 66 cout << divide(x,y); 77 }
Print Variable
1(gdb) print x
Output
1$1 = 20
Print Another Variable
1(gdb) print y
Output
1$2 = 0
Next Line
Execute the next source line without entering function calls.
1(gdb) next
Shortcut
1(gdb) n
Step Into Function
Enter the called function.
1(gdb) step
Shortcut
1(gdb) s
Continue Execution
1(gdb) continue
Shortcut
1(gdb) c
Backtrace
Display the function call stack.
1(gdb) backtrace
Shortcut
1(gdb) bt
Example
1#0 divide() 2#1 main()
Watch Variable
Pause execution when a variable changes.
1(gdb) watch x
Delete Breakpoint
1(gdb) delete 1
Quit GDB
1(gdb) quit
Assertions
Assertions verify assumptions during program execution.
Header
1#include <cassert>
Assertion Example
1#include <cassert> 2#include <iostream> 3 4using namespace std; 5 6int divide(int a, int b) 7{ 8 assert(b != 0); 9 10 return a / b; 11} 12 13int main() 14{ 15 cout << divide(20, 5); 16}
Output
14
Failed Assertion
1#include <cassert> 2 3int main() 4{ 5 int age = -5; 6 7 assert(age >= 0); 8}
Output
1Assertion failed
Assertions are useful during development to detect invalid states early.
Logging
Logging records useful information while a program runs.
Simple Logging
1#include <iostream> 2 3using namespace std; 4 5int main() 6{ 7 cout << "[INFO] Program Started\n"; 8 9 cout << "[INFO] Loading Data\n"; 10 11 cout << "[INFO] Finished Successfully\n"; 12}
Output
1[INFO] Program Started 2[INFO] Loading Data 3[INFO] Finished Successfully
Error Logging
1#include <iostream> 2 3using namespace std; 4 5int main() 6{ 7 cerr << "[ERROR] File not found\n"; 8}
Output
1[ERROR] File not found
Debug Logging
1#include <iostream> 2 3using namespace std; 4 5int main() 6{ 7 int x = 25; 8 9 cout << "[DEBUG] x = " << x << endl; 10}
Output
1[DEBUG] x = 25
Logging Levels
| Level | Purpose |
|---|---|
| DEBUG | Detailed diagnostic information |
| INFO | General application events |
| WARNING | Unexpected but recoverable situations |
| ERROR | Operation failed |
| FATAL | Program cannot continue |
Visual Studio Debugger
Visual Studio provides an integrated debugger.
Set Breakpoint
Click in the left margin beside a line number or press F9.
Start Debugging
Press
1F5
Step Over
1F10
Executes the current line without entering function calls.
Step Into
1F11
Enters the called function.
Continue
1F5
Resumes execution until the next breakpoint.
Watch Window
Monitor variables while stepping through the program.
Example
1x = 20 2y = 5 3sum = 25
Call Stack
Displays the sequence of function calls that led to the current execution point.
1main() 2 3↓ 4 5calculate() 6 7↓ 8 9add()
VS Code Debugger
VS Code uses the C/C++ extension together with GDB or LLDB.
Example launch.json
1{ 2 "version": "0.2.0", 3 "configurations": [ 4 { 5 "name": "Debug C++", 6 "type": "cppdbg", 7 "request": "launch", 8 "program": "${workspaceFolder}/app", 9 "cwd": "${workspaceFolder}", 10 "MIMode": "gdb", 11 "stopAtEntry": false 12 } 13 ] 14}
Start Debugging
Press
1F5
Step Controls
| Key | Action |
|---|---|
| F5 | Continue |
| F9 | Toggle Breakpoint |
| F10 | Step Over |
| F11 | Step Into |
| Shift + F11 | Step Out |
Common Debugging Example
Buggy Code
1#include <iostream> 2 3using namespace std; 4 5int main() 6{ 7 int numbers[5] = {1,2,3,4,5}; 8 9 cout << numbers[10]; 10}
Problem
1Array index out of bounds
Correct Code
1#include <iostream> 2 3using namespace std; 4 5int main() 6{ 7 int numbers[5] = {1,2,3,4,5}; 8 9 cout << numbers[4]; 10}
Memory Debugging
Null Pointer Example
1#include <iostream> 2 3using namespace std; 4 5int main() 6{ 7 int* ptr = nullptr; 8 9 if(ptr) 10 cout << *ptr; 11 else 12 cout << "Pointer is null"; 13}
Output
1Pointer is null
Using assert for Preconditions
1#include <cassert> 2 3int squareRootInput(int n) 4{ 5 assert(n >= 0); 6 7 return n; 8} 9 10int main() 11{ 12 squareRootInput(25); 13}
Debug vs Release Build
| Debug Build | Release Build |
|---|---|
| Includes debug symbols | Optimised for speed |
| Easier to debug | Harder to inspect |
| Larger executable | Smaller executable |
| Slower execution | Faster execution |
Common Debugging Mistakes
Forgetting Debug Symbols
Wrong
1g++ main.cpp
Correct
1g++ -g -O0 main.cpp -o app
Relying Only on cout
Use breakpoints, watch variables, and the debugger instead of excessive print statements.
Leaving Assertions Disabled During Testing
Assertions help detect invalid assumptions during development. Remember that assert is disabled when NDEBUG is defined.
Best Practices
- Compile debug builds with
-g -O0. - Use descriptive log messages.
- Keep functions small and focused.
- Check pointer validity before dereferencing.
- Use assertions for programmer errors, not user input validation.
- Prefer a debugger over guessing the cause of bugs.
- Reproduce bugs consistently before fixing them.
- Test edge cases after making changes.
Interview Questions
1. What is debugging?
The process of identifying, analysing, and fixing software defects.
2. What is GDB?
The GNU Debugger, a command-line tool for inspecting and controlling C/C++ program execution.
3. What is a breakpoint?
A marker that pauses program execution at a specified line so the program state can be inspected.
4. What is the difference between next and step in GDB?
nextexecutes the current line without entering called functions.stepenters the called function for line-by-line debugging.
5. What is an assertion?
A runtime check that verifies assumptions made by the program during development.
6. Why use logging?
To record application events and diagnostic information that help identify issues during development and in production.
7. What is the purpose of the call stack?
It shows the chain of active function calls leading to the current execution point.
8. Why compile with -g?
To include debugging symbols so debuggers can map machine instructions back to source code.
Module Summary
In this module, you learned:
- How to compile C++ programs with debugging symbols
- Basic GDB commands for breakpoints, stepping, variable inspection, and backtraces
- Using assertions to detect invalid program states
- Logging techniques with different log levels
- Debugging with Visual Studio and VS Code
- Common debugging mistakes and how to avoid them
- Best practices for diagnosing and fixing C++ program errors
These debugging techniques are essential for developing reliable, maintainable, and production-quality C++ applications.