C++ Debugging & Logging
Finding and fixing errors in your code
๐ What is Debugging & Logging?
Debugging helps find and fix errors in code. Logging records program behavior and events. Both are essential for developing reliable C++ applications.
#include <iostream>
using namespace std;
int main() {
cout << "Debug: Starting program" << endl;
int x = 10, y = 0;
cout << "Debug: x=" << x << ", y=" << y << endl;
return 0;
}
Debugging Techniques
Print Debugging
Use cout to track values
cout << "Debug: x = " << x << endl;
Assertions
Check conditions during runtime
#include <cassert>
assert(x > 0);
Logging
Record program events
ofstream log("app.log");
log << "Event occurred" << endl;
Debugger Tools
Use IDE debugging features
// Set breakpoints
// Step through code
// Watch variables
๐น Print Debugging
Inserting print statements (std::cout) traces execution flow and variable states. The factorial example shows recursive calls unfolding, revealing the programโs dynamic behavior. While primitive, print debugging is quick and effective for understanding runtime logic, especially when interactive debuggers are unavailable.
#include <iostream>
using namespace std;
int factorial(int n) {
cout << "DEBUG: factorial called with n = " << n << endl;
if (n <= 1) {
cout << "DEBUG: Base case reached, returning 1" << endl;
return 1;
}
int result = n * factorial(n - 1);
cout << "DEBUG: factorial(" << n << ") = " << result << endl;
return result;
}
int main() {
cout << "DEBUG: Program started" << endl;
int number = 5;
cout << "DEBUG: Calculating factorial of " << number << endl;
int result = factorial(number);
cout << "DEBUG: Final result = " << result << endl;
cout << "DEBUG: Program ended" << endl;
return 0;
}
Output:
DEBUG: Program started
DEBUG: Calculating factorial of 5
DEBUG: factorial called with n = 5
DEBUG: factorial called with n = 4
DEBUG: factorial called with n = 3
DEBUG: factorial called with n = 2
DEBUG: factorial called with n = 1
DEBUG: Base case reached, returning 1
DEBUG: factorial(2) = 2
DEBUG: factorial(3) = 6
DEBUG: factorial(4) = 24
DEBUG: factorial(5) = 120
DEBUG: Final result = 120
DEBUG: Program ended
๐น Using Assertions
Assertions assert in C++ validate critical assumptions during development, immediately halting execution when conditions fail to signal bugs early. They check essential preconditions like ensuring divisors are non-zero or array indices remain within bounds. While assertions are active in debug builds to catch errors during development, they're typically disabled in release builds for performance. This makes assertions a powerful complement to robust error handling, helping developers identify logical errors and unexpected states that might otherwise cause silent failures or incorrect results in production environments.
#include <iostream>
#include <cassert>
using namespace std;
double divide(double a, double b) {
// Assert that b is not zero
assert(b != 0 && "Division by zero!");
cout << "Dividing " << a << " by " << b << endl;
return a / b;
}
int arrayAccess(int arr[], int size, int index) {
// Assert valid array bounds
assert(index >= 0 && "Index cannot be negative!");
assert(index < size && "Index out of bounds!");
cout << "Accessing array[" << index << "]" << endl;
return arr[index];
}
int main() {
// This will work fine
cout << "Result: " << divide(10, 2) << endl;
int numbers[] = {1, 2, 3, 4, 5};
cout << "Value: " << arrayAccess(numbers, 5, 2) << endl;
// Uncomment to see assertion failure
// cout << divide(10, 0) << endl; // This will trigger assertion
return 0;
}
Output:
Dividing 10 by 2
Result: 5
Accessing array[2]
Value: 3
๐น Simple Logging System
A custom logger timestamps messages with severity levels (INFO, WARNING). This structures output for monitoring and debugging. Logs persist across sessions, providing a history of application behavior, which is invaluable for diagnosing issues in deployment environments.
#include <iostream>
#include <fstream>
#include <ctime>
#include <string>
using namespace std;
class Logger {
private:
ofstream logFile;
string getCurrentTime() {
time_t now = time(0);
char* timeStr = ctime(&now);
string result(timeStr);
result.pop_back(); // Remove newline
return result;
}
public:
Logger(const string& filename) {
logFile.open(filename, ios::app);
}
~Logger() {
if (logFile.is_open()) {
logFile.close();
}
}
void info(const string& message) {
log("INFO", message);
}
void warning(const string& message) {
log("WARNING", message);
}
void error(const string& message) {
log("ERROR", message);
}
private:
void log(const string& level, const string& message) {
string logEntry = "[" + getCurrentTime() + "] " + level + ": " + message;
// Write to file
if (logFile.is_open()) {
logFile << logEntry << endl;
}
// Also print to console
cout << logEntry << endl;
}
};
int main() {
Logger logger("app.log");
logger.info("Application started");
int x = 10, y = 5;
logger.info("Variables initialized: x=" + to_string(x) + ", y=" + to_string(y));
if (y == 0) {
logger.error("Division by zero attempted!");
return 1;
}
int result = x / y;
logger.info("Division result: " + to_string(result));
logger.warning("This is a warning message");
logger.info("Application ended successfully");
return 0;
}
Output:
[Mon Jan 15 14:30:25 2024] INFO: Application started
[Mon Jan 15 14:30:25 2024] INFO: Variables initialized: x=10, y=5
[Mon Jan 15 14:30:25 2024] INFO: Division result: 2
[Mon Jan 15 14:30:25 2024] WARNING: This is a warning message
[Mon Jan 15 14:30:25 2024] INFO: Application ended successfully
๐น Debugging Best Practices
Effective debugging combines tools: debuggers for inspection, assertions for invariants, and logs for history. Isolating issues via unit tests, reproducing failures, and understanding program state are key. Methodical approaches reduce time spent fixing bugs and improve overall code quality.
Debugging Strategies:
- Start small - Test small parts of code first
- Use meaningful variable names - Makes debugging easier
- Add debug prints strategically - At function entry/exit points
- Check boundary conditions - Test edge cases
- Use version control - Track changes that introduce bugs
Common Debugging Tools:
- GDB - GNU Debugger for command line
- Visual Studio Debugger - Integrated debugging
- Code::Blocks Debugger - IDE with debugging support
- Valgrind - Memory error detection
- Static analyzers - Find issues without running code
Log Levels:
- DEBUG - Detailed information for debugging
- INFO - General information about program flow
- WARNING - Something unexpected but not critical
- ERROR - Error conditions that need attention
- FATAL - Critical errors that stop the program