Skip to main content

C Debugging Techniques

This guide covers C programming debugging techniques, tools, and strategies for identifying and fixing issues in C code.

🎯 Debugging Fundamentals

Common C Debugging Challenges

  • Memory Issues: Segmentation faults, memory leaks, buffer overflows
  • Pointer Problems: Null pointers, dangling pointers, pointer arithmetic errors
  • Compilation Issues: Linker errors, undefined references
  • Runtime Errors: Logic errors, uninitialized variables, type mismatches

Debugging Methodology

  1. Reproduce the Issue: Create minimal test case
  2. Isolate the Problem: Binary search through code
  3. Form Hypothesis: Guess the root cause
  4. Test Hypothesis: Verify with debugging tools
  5. Fix and Verify: Apply fix and test thoroughly

🛠️ Debugging Tools

GDB (GNU Debugger)

# Compile with debug symbols
gcc -g program.c -o program

# Start GDB
gdb ./program

# Common GDB commands
(gdb) break main # Set breakpoint at main
(gdb) run # Start program
(gdb) next # Next line (step over)
(gdb) step # Next line (step into)
(gdb) continue # Continue execution
(gdb) print variable # Print variable value
(gdb) bt # Show backtrace
(gdb) info locals # Show local variables
(gdb) quit # Exit GDB

Advanced GDB Usage

// program.c
#include <stdio.h>
#include <stdlib.h>

void problematic_function(int* ptr, int size) {
for (int i = 0; i <= size; i++) { // Bug: should be i < size
ptr[i] = i * 2; // Buffer overflow
}
}

int main() {
int* data = malloc(sizeof(int) * 10);
if (!data) return 1;

problematic_function(data, 10);

free(data);
return 0;
}
# Debugging session with GDB
$ gdb ./program
(gdb) break problematic_function
(gdb) run
(gdb) print size
$1 = 10
(gdb) watch i
(gdb) continue
Hardware watchpoint 1: i

Old value = 9
New value = 10
0x0000000000400567 in problematic_function (ptr=0x601010, size=10)
(gdb) backtrace
#0 problematic_function (ptr=0x601010, size=10) at program.c:6
#1 0x0000000000400589 in main () at program.c:14
(gdb) quit

Valgrind (Memory Debugging)

# Install Valgrind
sudo apt-get install valgrind

# Check for memory leaks
valgrind --leak-check=full --show-leak-kinds=all ./program

# Check for memory errors
valgrind --tool=memcheck --track-origins=yes ./program

# Check for buffer overflows
valgrind --tool=address-sanitizer ./program

AddressSanitizer (ASan)

# Compile with AddressSanitizer
gcc -fsanitize=address -g program.c -o program

# Run the program
./program

# Output example:
==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000050
WRITE of size 4 at 0x602000000050 thread T0
#0 0x4007b7 in problematic_function program.c:6
#1 0x400839 in main program.c:14

Static Analysis Tools

# Using cppcheck
cppcheck --enable=all program.c

# Using clang-static-analyzer
scan-build gcc program.c

# Using splint
splint program.c

🔍 Debugging Techniques

#include <stdio.h>

void debug_function(int* data, int size) {
printf("DEBUG: Entering function with size=%d\n", size);

for (int i = 0; i < size; i++) {
printf("DEBUG: Processing index %d, value=%d\n", i, data[i]);

// Some processing
data[i] *= 2;

printf("DEBUG: New value at index %d: %d\n", i, data[i]);
}

printf("DEBUG: Exiting function\n");
}

// Conditional debugging
#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...)
#endif

void optimized_function(int* data, int size) {
DEBUG_PRINT("DEBUG: Processing %d elements\n", size);

for (int i = 0; i < size; i++) {
data[i] *= 2;
DEBUG_PRINT("DEBUG: Element %d processed\n", i);
}
}

Assertion-Based Debugging

#include <assert.h>
#include <stdio.h>

int divide(int a, int b) {
assert(b != 0 && "Division by zero!");
return a / b;
}

void process_array(int* arr, int size) {
assert(arr != NULL && "Array pointer is NULL!");
assert(size > 0 && "Array size must be positive!");

for (int i = 0; i < size; i++) {
assert(i >= 0 && i < size && "Array index out of bounds!");
arr[i] *= 2;
}
}

// Custom assertion macro
#define ASSERT(condition, message) \
do { \
if (!(condition)) { \
fprintf(stderr, "Assertion failed: %s, file %s, line %d\n", \
message, __FILE__, __LINE__); \
abort(); \
} \
} while(0)

void safe_function(int* ptr) {
ASSERT(ptr != NULL, "Pointer cannot be null");
// Function logic
}

Memory Debugging

#include <stdlib.h>
#include <string.h>

// Custom memory tracker
typedef struct {
void* ptr;
size_t size;
const char* file;
int line;
} AllocationRecord;

#define MAX_ALLOCATIONS 1000
static AllocationRecord allocations[MAX_ALLOCATIONS];
static int allocation_count = 0;

void* debug_malloc(size_t size, const char* file, int line) {
void* ptr = malloc(size);
if (ptr && allocation_count < MAX_ALLOCATIONS) {
allocations[allocation_count].ptr = ptr;
allocations[allocation_count].size = size;
allocations[allocation_count].file = file;
allocations[allocation_count].line = line;
allocation_count++;
}
return ptr;
}

void debug_free(void* ptr) {
if (!ptr) return;

for (int i = 0; i < allocation_count; i++) {
if (allocations[i].ptr == ptr) {
// Move last element to current position
allocations[i] = allocations[allocation_count - 1];
allocation_count--;
break;
}
}

free(ptr);
}

void print_leaks() {
printf("Memory leaks detected:\n");
for (int i = 0; i < allocation_count; i++) {
printf("Leak: %p (%zu bytes) at %s:%d\n",
allocations[i].ptr,
allocations[i].size,
allocations[i].file,
allocations[i].line);
}
}

// Macros for automatic file/line tracking
#define MALLOC(size) debug_malloc(size, __FILE__, __LINE__)
#define FREE(ptr) debug_free(ptr)

// Usage
void test_memory_tracking() {
int* ptr1 = MALLOC(sizeof(int) * 10);
int* ptr2 = MALLOC(sizeof(int) * 20);

FREE(ptr1);
// ptr2 is not freed - will be reported as leak

print_leaks();
}

🐛 Common Bug Patterns

Segmentation Faults

// Common causes and fixes

// 1. Null pointer dereference
void fix_null_pointer() {
int* ptr = NULL;

// Bad: Dereferencing null pointer
// *ptr = 42; // Segmentation fault

// Good: Check for null
if (ptr != NULL) {
*ptr = 42;
}
}

// 2. Array out of bounds
void fix_array_bounds() {
int arr[10];

// Bad: Out of bounds access
// arr[10] = 42; // Segmentation fault

// Good: Bounds checking
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
}

// 3. Use after free
void fix_use_after_free() {
int* ptr = malloc(sizeof(int));
*ptr = 42;
free(ptr);

// Bad: Using freed memory
// *ptr = 100; // Segmentation fault

// Good: Set pointer to NULL after free
ptr = NULL;
if (ptr != NULL) {
*ptr = 100;
}
}

// 4. Stack overflow
void fix_stack_overflow() {
// Bad: Too large stack allocation
// char huge_array[10000000]; // Stack overflow

// Good: Use heap allocation
char* huge_array = malloc(10000000);
if (huge_array) {
// Use array
free(huge_array);
}
}

Memory Leaks

// Common memory leak patterns and fixes

// 1. Forgetting to free
void fix_memory_leak() {
// Bad: Memory leak
// int* ptr = malloc(sizeof(int));
// *ptr = 42;
// return; // Forgot to free

// Good: Always free allocated memory
int* ptr = malloc(sizeof(int));
if (ptr) {
*ptr = 42;
free(ptr);
}
}

// 2. Lost pointer
void fix_lost_pointer() {
int* ptr = malloc(sizeof(int) * 10);
ptr = malloc(sizeof(int) * 20); // Lost first allocation

// Good: Free before reassigning
int* ptr = malloc(sizeof(int) * 10);
if (ptr) {
free(ptr);
}
ptr = malloc(sizeof(int) * 20);
if (ptr) {
free(ptr);
}
}

// 3. Leak in error handling
void fix_error_handling_leak() {
int* ptr1 = malloc(sizeof(int) * 10);
int* ptr2 = malloc(sizeof(int) * 10);

if (!ptr1 || !ptr2) {
// Bad: Incomplete cleanup
// return;

// Good: Clean up all allocations
if (ptr1) free(ptr1);
if (ptr2) free(ptr2);
return;
}

// Use pointers
free(ptr1);
free(ptr2);
}

Logic Errors

// Common logic errors and fixes

// 1. Off-by-one errors
void fix_off_by_one() {
int arr[10];

// Bad: Off-by-one error
// for (int i = 0; i <= 10; i++) { // i = 10 is out of bounds

// Good: Correct loop bounds
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
}

// 2. Integer overflow
void fix_integer_overflow() {
int max_int = 2147483647;

// Bad: Integer overflow
// int result = max_int + 1; // Overflow

// Good: Check for overflow
if (max_int > INT_MAX - 1) {
printf("Would overflow\n");
} else {
int result = max_int + 1;
}
}

// 3. Uninitialized variables
void fix_uninitialized() {
int x; // Uninitialized

// Bad: Using uninitialized variable
// printf("%d\n", x);

// Good: Initialize variables
int y = 0;
printf("%d\n", y);
}

🔧 Advanced Debugging

Core Dump Analysis

# Enable core dumps
ulimit -c unlimited

# Run program that crashes
./program

# Analyze core dump with GDB
gdb ./program core

# In GDB
(gdb) bt full # Full backtrace
(gdb) info registers # Register values
(gdb) x/20x $esp # Examine stack memory
(gdb) quit

Signal Handling

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void signal_handler(int sig) {
printf("Received signal %d\n", sig);

switch (sig) {
case SIGSEGV:
printf("Segmentation fault occurred\n");
break;
case SIGFPE:
printf("Floating point exception occurred\n");
break;
case SIGINT:
printf("Interrupt received\n");
break;
}

exit(1);
}

void setup_signal_handlers() {
signal(SIGSEGV, signal_handler);
signal(SIGFPE, signal_handler);
signal(SIGINT, signal_handler);
}

void test_signal_handling() {
setup_signal_handlers();

// This will trigger SIGSEGV
int* ptr = NULL;
*ptr = 42;
}

Debugging Macros

// Comprehensive debugging macros
#include <stdio.h>
#include <time.h>

// Timestamp macro
#define TIMESTAMP() printf("[%ld] ", (long)time(NULL))

// Function entry/exit macros
#define FUNCTION_ENTRY() \
TIMESTAMP(); printf("ENTER: %s\n", __func__)

#define FUNCTION_EXIT() \
TIMESTAMP(); printf("EXIT: %s\n", __func__)

// Variable value macro
#define DUMP_VAR(var) \
TIMESTAMP(); printf("%s = %d\n", #var, var)

// Array dump macro
#define DUMP_ARRAY(arr, size) \
do { \
TIMESTAMP(); printf("%s = [", #arr); \
for (int i = 0; i < size; i++) { \
printf("%d", arr[i]); \
if (i < size - 1) printf(", "); \
} \
printf("]\n"); \
} while(0)

// Usage
void debugged_function(int* data, int size) {
FUNCTION_ENTRY();
DUMP_VAR(size);
DUMP_ARRAY(data, size);

for (int i = 0; i < size; i++) {
data[i] *= 2;
DUMP_VAR(data[i]);
}

FUNCTION_EXIT();
}

📊 Performance Debugging

Profiling with Gprof

# Compile with profiling support
gcc -pg program.c -o program

# Run program to generate profile data
./program

# Generate profile report
gprof program gmon.out > profile.txt

# View profile report
cat profile.txt

Timing Measurements

#include <time.h>
#include <sys/time.h>

// High-resolution timing
double get_time_microseconds() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000000.0 + tv.tv_usec;
}

// Function timing macro
#define TIME_FUNCTION(func, ...) \
do { \
double start = get_time_microseconds(); \
func(__VA_ARGS__); \
double end = get_time_microseconds(); \
printf("Function %s took %.2f microseconds\n", #func, end - start); \
} while(0)

// Usage
void expensive_function(int n) {
// Simulate expensive operation
volatile int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
}

void test_timing() {
TIME_FUNCTION(expensive_function, 1000000);
}

🎯 Debugging Best Practices

Defensive Programming

#include <assert.h>
#include <errno.h>
#include <string.h>

// Safe string operations
void safe_string_copy(char* dest, const char* src, size_t dest_size) {
assert(dest != NULL);
assert(src != NULL);
assert(dest_size > 0);

strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0';
}

// Safe file operations
FILE* safe_fopen(const char* filename, const char* mode) {
FILE* file = fopen(filename, mode);
if (!file) {
fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno));
return NULL;
}
return file;
}

// Safe memory allocation
void* safe_malloc(size_t size) {
void* ptr = malloc(size);
if (!ptr) {
fprintf(stderr, "Failed to allocate %zu bytes\n", size);
exit(EXIT_FAILURE);
}
return ptr;
}

Error Handling Patterns

#include <stdio.h>
#include <stdlib.h>

// Error codes
typedef enum {
ERROR_SUCCESS = 0,
ERROR_NULL_POINTER,
ERROR_INVALID_PARAMETER,
ERROR_MEMORY_ALLOCATION,
ERROR_FILE_NOT_FOUND
} ErrorCode;

// Error handling macro
#define CHECK_ERROR(condition, error_code, message) \
do { \
if (!(condition)) { \
fprintf(stderr, "Error: %s\n", message); \
return error_code; \
} \
} while(0)

// Function with error handling
ErrorCode process_data(int* data, int size) {
CHECK_ERROR(data != NULL, ERROR_NULL_POINTER, "Data pointer is NULL");
CHECK_ERROR(size > 0, ERROR_INVALID_PARAMETER, "Size must be positive");

// Process data
for (int i = 0; i < size; i++) {
data[i] *= 2;
}

return ERROR_SUCCESS;
}

// Usage
void test_error_handling() {
int data[100];
ErrorCode result = process_data(data, 100);

if (result != ERROR_SUCCESS) {
fprintf(stderr, "Processing failed with error %d\n", result);
} else {
printf("Processing succeeded\n");
}
}

🔗 Language-Specific Debugging

📍 Visit Us

🏫 VD Computer Tuition Surat

VD Computer Tuition
📍 Address
2/66 Faram Street, Rustompura
Surat395002, Gujarat, India
📞 Phone / WhatsApp
+91 84604 41384
🌐 Website

Computer Classes & Tuition — Areas We Serve in Surat

AdajanAlthanAmroliAthwaAthwalinesBhagalBhatarBhestanCanal RoadChowkCitylightDumasGaurav PathGhod Dod RoadHaziraJahangirpuraKamrejKapodraKatargamLimbayatMagdallaMajura GateMota VarachhaNanpuraNew CitylightOlpadPalPandesaraParle PointPiplodPunaRanderRing RoadRustampuraSachinSalabatpuraSarthanaSosyo CircleUdhnaVarachhaVed RoadVesuVIP Road
📞 Call Sir💬 WhatsApp Sir