cpp
exercises
exercises.cpp⚙️cpp
/**
* GDB Debugging Exercises
*
* These programs contain bugs that you need to find using GDB.
* Compile with: g++ -g -O0 exercises.cpp -o exercises
* Debug with: gdb ./exercises
*/
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
using std::cout;
using std::endl;
// ============================================================================
// EXERCISE 1: Find the Off-by-One Error
// ============================================================================
/*
* This function should calculate the sum of an array.
* Use GDB to find why it gives wrong results.
*
* Debug steps:
* 1. Set breakpoint at the function
* 2. Step through the loop
* 3. Watch the loop counter and array access
*/
int sumArray_buggy(int arr[], int size) {
int sum = 0;
for (int i = 0; i <= size; i++) { // BUG: <= should be <
sum += arr[i];
}
return sum;
}
void exercise1() {
cout << "=== EXERCISE 1: Off-by-One Error ===" << endl;
int numbers[] = {1, 2, 3, 4, 5};
int result = sumArray_buggy(numbers, 5);
cout << "Sum (buggy): " << result << " (expected: 15)" << endl;
// GDB hints:
// (gdb) break sumArray_buggy
// (gdb) run
// (gdb) display i
// (gdb) display sum
// (gdb) next (repeatedly)
// Notice: when i=5, we access arr[5] which is out of bounds!
}
// ============================================================================
// EXERCISE 2: Find the Null Pointer Dereference
// ============================================================================
/*
* This function processes a linked list but crashes.
* Use GDB to find where the null pointer is dereferenced.
*
* Debug steps:
* 1. Run until crash
* 2. Examine the backtrace
* 3. Print pointer values
*/
struct ListNode {
int value;
ListNode* next;
ListNode(int v) : value(v), next(nullptr) {}
};
int findValue_buggy(ListNode* head, int target) {
ListNode* current = head;
while (current->value != target) { // BUG: Should check if current is null first
current = current->next;
}
return current->value;
}
void exercise2() {
cout << "\n=== EXERCISE 2: Null Pointer Dereference ===" << endl;
ListNode* head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
// This will crash because 99 doesn't exist
// int result = findValue_buggy(head, 99);
// cout << "Found: " << result << endl;
cout << "Uncomment the buggy code to see the crash" << endl;
cout << "Use GDB to find where it crashes" << endl;
// GDB hints:
// (gdb) run
// Program crashes with SIGSEGV
// (gdb) backtrace
// (gdb) print current
// current = 0x0 (null!)
// Cleanup
while (head) {
ListNode* temp = head;
head = head->next;
delete temp;
}
}
// ============================================================================
// EXERCISE 3: Find the Uninitialized Variable
// ============================================================================
/*
* This function has undefined behavior due to uninitialized variable.
* Use GDB to observe the random value.
*
* Debug steps:
* 1. Set breakpoint at the function
* 2. Print 'total' before the loop
* 3. Notice it has garbage value
*/
int calculateTotal_buggy(const std::vector<int>& values) {
int total; // BUG: Not initialized to 0
for (size_t i = 0; i < values.size(); i++) {
total += values[i];
}
return total;
}
void exercise3() {
cout << "\n=== EXERCISE 3: Uninitialized Variable ===" << endl;
std::vector<int> data = {10, 20, 30};
int result = calculateTotal_buggy(data);
cout << "Total (buggy): " << result << " (expected: 60, but varies!)" << endl;
// GDB hints:
// (gdb) break calculateTotal_buggy
// (gdb) run
// (gdb) print total
// $1 = 32767 (or some random value)
}
// ============================================================================
// EXERCISE 4: Find the Memory Leak
// ============================================================================
/*
* This function allocates memory but doesn't always free it.
* Use GDB to trace the execution path.
*
* Note: Use Valgrind for better memory leak detection:
* valgrind --leak-check=full ./exercises
*/
char* createMessage_buggy(bool success) {
char* buffer = new char[100];
if (success) {
strcpy(buffer, "Operation successful");
return buffer;
} else {
strcpy(buffer, "Operation failed");
// BUG: buffer is returned but never freed by caller
// Also: if we add 'delete[] buffer' here, we'd return freed memory!
return buffer;
}
}
void exercise4() {
cout << "\n=== EXERCISE 4: Memory Leak ===" << endl;
// This leaks memory every call!
char* msg1 = createMessage_buggy(true);
char* msg2 = createMessage_buggy(false);
cout << "Message 1: " << msg1 << endl;
cout << "Message 2: " << msg2 << endl;
// Caller should free, but often forgets
delete[] msg1;
delete[] msg2;
cout << "Run with Valgrind to detect leaks if delete lines removed" << endl;
// Valgrind command:
// valgrind --leak-check=full ./exercises
}
// ============================================================================
// EXERCISE 5: Find the Infinite Loop
// ============================================================================
/*
* This function has an infinite loop.
* Use GDB to break into it and find the problem.
*
* Debug steps:
* 1. Run the program
* 2. Ctrl+C to break
* 3. Examine variable values
*/
void processData_buggy(int* data, int size) {
int i = 0;
while (i < size) {
data[i] = data[i] * 2;
// BUG: forgot i++; causing infinite loop
// i++;
}
}
void exercise5() {
cout << "\n=== EXERCISE 5: Infinite Loop ===" << endl;
int data[] = {1, 2, 3, 4, 5};
// WARNING: This will hang!
// processData_buggy(data, 5);
cout << "Uncomment the buggy code to see infinite loop" << endl;
cout << "Use Ctrl+C in GDB, then 'where' to see location" << endl;
// GDB hints:
// (gdb) run
// (program hangs)
// Ctrl+C
// (gdb) where
// (gdb) print i
// $1 = 0 (never increments!)
}
// ============================================================================
// EXERCISE 6: Find the Buffer Overflow
// ============================================================================
/*
* This function writes beyond the buffer.
* Use GDB to watch memory corruption.
*/
void copyString_buggy(char* dest, const char* src, int destSize) {
int i = 0;
while (src[i] != '\0') { // BUG: Should also check i < destSize-1
dest[i] = src[i];
i++;
}
dest[i] = '\0';
}
void exercise6() {
cout << "\n=== EXERCISE 6: Buffer Overflow ===" << endl;
char buffer[10];
const char* longString = "This string is way too long for the buffer!";
// This will overflow!
// copyString_buggy(buffer, longString, 10);
// cout << "Buffer: " << buffer << endl;
cout << "Uncomment to see overflow (may crash or corrupt memory)" << endl;
// GDB hints:
// (gdb) break copyString_buggy
// (gdb) run
// (gdb) watch i
// (gdb) continue (repeatedly)
// Notice when i > 9, we're writing past buffer
// Better: use address sanitizer
// g++ -fsanitize=address -g exercises.cpp -o exercises
}
// ============================================================================
// EXERCISE 7: Find the Logic Error
// ============================================================================
/*
* This sorting function has a logic error.
* Use GDB to watch the array during sorting.
*/
void bubbleSort_buggy(int arr[], int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) { // BUG: Should be j < n-1-i
if (arr[j] > arr[j+1]) { // Also: j+1 can be out of bounds
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
void exercise7() {
cout << "\n=== EXERCISE 7: Logic Error in Sort ===" << endl;
int arr[] = {5, 2, 8, 1, 9};
int n = 5;
cout << "Before: ";
for (int i = 0; i < n; i++) cout << arr[i] << " ";
cout << endl;
// bubbleSort_buggy(arr, n); // May crash or give wrong results
cout << "Uncomment to see buggy sort" << endl;
// GDB hints:
// (gdb) break bubbleSort_buggy
// (gdb) run
// (gdb) print arr[0]@5
// (gdb) next (watch the array change)
// Notice: when j=4, arr[j+1] = arr[5] is out of bounds!
}
// ============================================================================
// EXERCISE 8: Find the Race Condition
// ============================================================================
/*
* This multi-threaded code has a race condition.
* Use GDB to observe inconsistent results.
*
* Note: Race conditions are hard to debug - results are non-deterministic
*/
#include <thread>
int globalCounter = 0;
void increment_buggy() {
for (int i = 0; i < 100000; i++) {
globalCounter++; // BUG: Not thread-safe
}
}
void exercise8() {
cout << "\n=== EXERCISE 8: Race Condition ===" << endl;
globalCounter = 0;
std::thread t1(increment_buggy);
std::thread t2(increment_buggy);
t1.join();
t2.join();
cout << "Counter: " << globalCounter << " (expected: 200000)" << endl;
cout << "Run multiple times to see different results!" << endl;
// GDB hints:
// (gdb) break increment_buggy
// (gdb) run
// (gdb) info threads
// (gdb) thread apply all print globalCounter
// Notice: both threads read/write globalCounter unsafely
}
// ============================================================================
// EXERCISE 9: Find the Double Free
// ============================================================================
/*
* This code frees memory twice.
* Use address sanitizer to detect it.
*/
void doubleFree_buggy() {
int* ptr = new int(42);
delete ptr;
// ... some code that might set ptr = nullptr ...
// BUG: ptr still points to freed memory
// delete ptr; // Double free!
}
void exercise9() {
cout << "\n=== EXERCISE 9: Double Free ===" << endl;
// doubleFree_buggy(); // Will crash with double free
cout << "Uncomment to see double-free crash" << endl;
cout << "Compile with: g++ -fsanitize=address -g exercises.cpp" << endl;
// GDB hints after crash:
// (gdb) backtrace
// Shows the free() that caused the error
}
// ============================================================================
// EXERCISE 10: Complex Debugging Challenge
// ============================================================================
/*
* This code has multiple bugs. Find them all!
*
* Bugs:
* 1. Array index out of bounds
* 2. Potential null pointer
* 3. Logic error in calculation
*/
struct Student {
std::string name;
int* grades; // Dynamic array of grades
int numGrades;
Student(const std::string& n, int num) : name(n), numGrades(num) {
grades = new int[numGrades];
}
~Student() {
delete[] grades;
}
double average_buggy() const {
int sum = 0;
for (int i = 0; i <= numGrades; i++) { // BUG 1: <= should be <
sum += grades[i];
}
return sum / numGrades; // BUG 2: Integer division
}
};
Student* findStudent_buggy(Student** students, int count, const std::string& name) {
for (int i = 0; i < count; i++) {
if (students[i]->name == name) { // BUG 3: students[i] might be null
return students[i];
}
}
return nullptr;
}
void exercise10() {
cout << "\n=== EXERCISE 10: Multiple Bugs ===" << endl;
Student* s1 = new Student("Alice", 3);
s1->grades[0] = 85;
s1->grades[1] = 90;
s1->grades[2] = 88;
// cout << "Average: " << s1->average_buggy() << endl; // Has bugs
Student* students[3] = {s1, nullptr, nullptr}; // Some are null
// Student* found = findStudent_buggy(students, 3, "Bob"); // Crashes
cout << "Uncomment buggy code and use GDB to find all bugs" << endl;
delete s1;
// GDB hints:
// 1. For average_buggy:
// (gdb) break Student::average_buggy
// (gdb) print i (watch it go to numGrades)
//
// 2. For integer division:
// (gdb) print sum
// (gdb) print numGrades
// (gdb) print sum / numGrades (integer result!)
//
// 3. For findStudent_buggy:
// (gdb) break findStudent_buggy
// (gdb) print students[1]
// $1 = 0x0 (null!)
}
// ============================================================================
// MAIN
// ============================================================================
int main() {
cout << "╔══════════════════════════════════════════════════════════════╗" << endl;
cout << "║ GDB DEBUGGING EXERCISES ║" << endl;
cout << "╚══════════════════════════════════════════════════════════════╝" << endl;
cout << "\nThese exercises contain bugs for you to find using GDB.\n" << endl;
exercise1();
exercise2();
exercise3();
exercise4();
exercise5();
exercise6();
exercise7();
exercise8();
exercise9();
exercise10();
cout << "\n═══════════════════════════════════════════════════════════════" << endl;
cout << "Debug commands:" << endl;
cout << " g++ -g -O0 exercises.cpp -o exercises -pthread" << endl;
cout << " gdb ./exercises" << endl;
cout << endl;
cout << "For memory bugs:" << endl;
cout << " g++ -fsanitize=address -g exercises.cpp -o exercises -pthread" << endl;
cout << " valgrind --leak-check=full ./exercises" << endl;
return 0;
}