cpp

exercises

exercises.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;
}
Exercises - C++ Tutorial | DeepML