Skip to main content

Overview

The 0x03-debugging module teaches essential debugging skills. You’ll learn to identify bugs, use debugging tools, fix logic errors, and handle edge cases. Debugging is a critical skill - writing code is only half the job; finding and fixing bugs is the other half.

What is Debugging?

Debugging is the process of finding and fixing errors (bugs) in your code. Common bug types include:
  • Syntax errors: Code that doesn’t compile
  • Logic errors: Code that compiles but produces wrong results
  • Runtime errors: Code that crashes during execution
  • Edge case errors: Code that fails with specific inputs

Header File for Debugging

main.h
#ifndef MAIN_H
#define MAIN_H

#include <stdio.h>

void positive_or_negative(int i);
int largest_number(int a, int b, int c);
int convert_day(int month, int day);
void print_remaining_days(int month, int day, int year);

#endif /* MAIN_H */
The #ifndef, #define, and #endif directives create an include guard, preventing the header from being included multiple times.

Testing Functions

Test Case - Zero Value

0-main.c
#include "main.h"

/**
* main - tests function that prints if integer is positive or negative
* Return: 0
*/

int main(void)
{
int i;

i = 0;
positive_or_negative(i);

return (0);
}
Test with boundary values like 0, negative numbers, and positive numbers to ensure your function handles all cases correctly.

Fixing Infinite Loops

The Bug

Infinite loops are common errors where the loop condition never becomes false:
/* BUGGY CODE - Don't use! */
int i = 0;
while (i < 10)
{
    putchar(i);
    /* Missing i++ causes infinite loop! */
}

The Fix

1-main.c
#include <stdio.h>

/**
* main - causes an infinite loop
* Return: 0
*/

int main(void)
{
int i;

printf("Infinite loop incoming :(\n");

i = 0;
/*
*while (i < 10)
*{
*putchar(i);
*}
*/
printf("Infinite loop avoided! \\o/\n");

return (0);
}
Commenting out problematic code with /* */ is one debugging technique. The code is preserved for reference but won’t execute.
Preventing infinite loops:
  1. Always update the loop control variable
  2. Ensure the condition will eventually be false
  3. Add safety counters for complex loops

Logic Errors

Finding the Largest Number

Logic errors are subtle - the code compiles but produces incorrect results.
2-largest_number.c
#include "main.h"

/**
 * largest_number - returns the largest of 3 numbers
 * @a: first integer
 * @b: second integer
 * @c: third integer
 * Return: largest number
 */

int largest_number(int a, int b, int c)
{
int largest;

if (a > b && a > c)
{
largest = a;
}
else if (b > a && b > c)
{
largest = b;
}
else if (c > b)
{
largest = c;
}
else
{
largest = b;
}

return (largest);
}
Edge case issue: What if two or all three numbers are equal?
  • If a = 5, b = 5, c = 3:
    • First condition fails: a > b is false
    • Second condition fails: b > a is false
    • Third condition: c > b is false
    • Falls to else: returns b (correct!)
  • If a = 5, b = 3, c = 5:
    • First condition fails: a > c is false
    • Second condition fails: b > a is false
    • Third condition: c > b is true
    • Returns c (correct!)
The else clause handles equal values correctly.
Better approach using >= for clarity:
if (a >= b && a >= c)
    largest = a;
else if (b >= c)
    largest = b;
else
    largest = c;

Complex Debugging - Leap Years

Date Validation Bug

3-print_remaining_days.c
#include <stdio.h>
#include "main.h"

/**
* print_remaining_days - takes a date and prints how many days are
* left in the year, taking leap years into account
* @month: month in number format
* @day: day of month
* @year: year
* Return: void
*/

void print_remaining_days(int month, int day, int year)
{
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
if (month > 2 && day >= 60)
{
day++;
}

printf("Day of the year: %d\n", day);
printf("Remaining days: %d\n", 366 - day);
}
else
{
if (month == 2 && day == 60)
{
printf("Invalid date: %02d/%02d/%04d\n", month, day - 31, year);
}
else
{
printf("Day of the year: %d\n", day);
printf("Remaining days: %d\n", 365 - day);
}
}
}

Understanding Leap Year Logic

A year is a leap year if:
  1. Divisible by 4 AND
  2. NOT divisible by 100 OR
  3. Divisible by 400
Examples:
  • 2000: Leap year (divisible by 400)
  • 1900: Not leap year (divisible by 100, not by 400)
  • 2024: Leap year (divisible by 4, not by 100)
The expression (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) uses operator precedence:
  • && binds tighter than ||
  • Equivalent to: ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)

Debugging Techniques

1. Print Debugging

Add printf statements to track values:
int largest_number(int a, int b, int c)
{
    printf("DEBUG: a=%d, b=%d, c=%d\n", a, b, c);
    
    int largest;
    
    if (a > b && a > c)
    {
        printf("DEBUG: choosing a\n");
        largest = a;
    }
    /* ... */
    
    printf("DEBUG: returning %d\n", largest);
    return (largest);
}
Prefix debug output with “DEBUG:” so you can easily find and remove it later.

2. Rubber Duck Debugging

Explain your code line-by-line to someone (or a rubber duck). Often, you’ll spot the bug while explaining.

3. Binary Search Debugging

Comment out half the code to isolate which section contains the bug:
/* First half works? */
code_line_1();
code_line_2();

/* Second half works? */
// code_line_3();
// code_line_4();

4. Test Edge Cases

/* Test with 0 */
result = function(0);

/* Test with -1 */
result = function(-1);

/* Test with MAX */
result = function(INT_MAX);

Common Bug Patterns

/* Bug: Misses last element */
for (i = 0; i < 10; i++)
    array[i+1] = 0;  /* Array index out of bounds! */

/* Fix: */
for (i = 0; i < 10; i++)
    array[i] = 0;
/* Bug */
int total;
total = total + 5;  /* total has garbage value! */

/* Fix */
int total = 0;
total = total + 5;
/* Bug */
if (x = 10)  /* Assigns 10 to x, always true! */
    printf("x is 10");

/* Fix */
if (x == 10)  /* Compares x to 10 */
    printf("x is 10");
/* Bug: Falls through */
switch (value)
{
    case 1:
        printf("One");
        /* Missing break! */
    case 2:
        printf("Two");  /* Executes even if value is 1 */
        break;
}

/* Fix */
switch (value)
{
    case 1:
        printf("One");
        break;
    case 2:
        printf("Two");
        break;
}

Compilation for Debugging

# Compile with debugging symbols
gcc -g -Wall -Werror -Wextra -pedantic -std=gnu89 main.c 2-largest_number.c -o debug

# Run with gdb debugger
gdb ./debug
The -g flag includes debugging information that tools like gdb can use.

Using GDB (GNU Debugger)

gdb ./program

Best Debugging Practices

Start simpleTest each function in isolation before combining them. Write small test programs.
Read error messages carefullyCompiler errors often tell you exactly what’s wrong and which line. Read the entire message.
Check assumptionsDon’t assume variables have expected values. Print them to verify.
Take breaksIf you’re stuck for more than 30 minutes, take a break. Fresh eyes spot bugs faster.

Testing Checklist

Before considering code complete:
  • Test with normal inputs
  • Test with edge cases (0, negative, maximum)
  • Test with invalid inputs
  • Test with equal values
  • Check for memory leaks
  • Verify all return paths
  • Check loop boundaries
  • Compile with all warnings enabled

Prevention is Better Than Cure

Write code to avoid bugs:
  1. Initialize all variables
  2. Check function return values
  3. Validate input parameters
  4. Use meaningful variable names
  5. Keep functions simple and focused
  6. Comment complex logic

Build docs developers (and LLMs) love