Skip to content

C Common Mistakes

This guide covers common C programming mistakes and how to avoid them, with practical solutions and examples.

🚨 Most Common C Errors

1. Memory Leaks

Forgetting to free allocated memory or using freed memory.

Problem

// Bad: Memory leak
#include <stdlib.h>
#include <stdio.h>

void memory_leak_example() {
    int* ptr = (int*)malloc(sizeof(int) * 100);
    ptr[0] = 42;
    // Forgot to free ptr!
}

void use_freed_memory() {
    int* ptr = (int*)malloc(sizeof(int));
    *ptr = 42;
    free(ptr);
    printf("%d\n", *ptr);  // Using freed memory!
}

Solutions

// Good: Proper memory management
#include <stdlib.h>
#include <stdio.h>

void proper_memory_management() {
    int* ptr = (int*)malloc(sizeof(int) * 100);
    if (ptr == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return;
    }

    ptr[0] = 42;
    printf("%d\n", ptr[0]);
    free(ptr);  // Always free allocated memory
    ptr = NULL;  // Avoid dangling pointers
}

// Better: Use RAII-style pattern
typedef struct {
    int* data;
    size_t size;
} SafeArray;

SafeArray* create_safe_array(size_t size) {
    SafeArray* arr = (SafeArray*)malloc(sizeof(SafeArray));
    if (arr == NULL) return NULL;

    arr->data = (int*)malloc(size * sizeof(int));
    if (arr->data == NULL) {
        free(arr);
        return NULL;
    }

    arr->size = size;
    return arr;
}

void destroy_safe_array(SafeArray* arr) {
    if (arr != NULL) {
        free(arr->data);
        free(arr);
    }
}

2. Buffer Overflow

Writing beyond array bounds or using unsafe string functions.

Problem

// Bad: Buffer overflow vulnerability
#include <string.h>
#include <stdio.h>

void buffer_overflow() {
    char buffer[10];
    strcpy(buffer, "This string is too long");  // Overflow!
    printf("%s\n", buffer);
}

// Bad: Array bounds violation
void array_bounds_violation() {
    int arr[5] = {1, 2, 3, 4, 5};
    for (int i = 0; i <= 5; i++) {  // Should be i < 5
        printf("%d\n", arr[i]);  // Reads beyond array!
    }
}

Solutions

// Good: Safe string operations
#include <string.h>
#include <stdio.h>

void safe_string_operations() {
    char buffer[10];
    strncpy(buffer, "This string is too long", sizeof(buffer) - 1);
    buffer[sizeof(buffer) - 1] = '\0';  // Ensure null termination
    printf("%s\n", buffer);
}

// Good: Proper array bounds
void safe_array_access() {
    int arr[5] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    for (int i = 0; i < size; i++) {  // Correct bounds
        printf("%d\n", arr[i]);
    }
}

3. Uninitialized Variables

Using variables before initialization leads to undefined behavior.

Problem

// Bad: Uninitialized variables
int uninitialized_example() {
    int x, y, z;
    printf("%d\n", x + y + z);  // Undefined behavior!

    int* ptr;
    *ptr = 42;  // Writing to uninitialized pointer!
}

Solutions

// Good: Always initialize variables
int initialized_example() {
    int x = 0, y = 0, z = 0;
    printf("%d\n", x + y + z);

    int* ptr = NULL;
    if (ptr != NULL) {
        *ptr = 42;
    }
}

// Better: Use compiler warnings
// Compile with: gcc -Wall -Wextra -Werror

4. Integer Overflow

Arithmetic operations exceeding the range of data types.

Problem

// Bad: Integer overflow
#include <limits.h>
#include <stdio.h>

void integer_overflow() {
    int max_int = INT_MAX;
    int result = max_int + 1;  // Overflow!
    printf("%d\n", result);  // Undefined behavior
}

Solutions

// Good: Check for overflow
#include <limits.h>
#include <stdio.h>

void safe_integer_operations() {
    int max_int = INT_MAX;
    int result;

    // Check before addition
    if (max_int > INT_MAX - 1) {
        printf("Would overflow\n");
        result = INT_MAX;
    } else {
        result = max_int + 1;
    }

    printf("%d\n", result);
}

// Better: Use larger data types
void safe_with_larger_type() {
    long long max_int = INT_MAX;
    long long result = max_int + 1;  // No overflow
    printf("%lld\n", result);
}

5. Pointer Errors

Incorrect pointer usage leading to crashes or undefined behavior.

Problem

// Bad: Pointer errors
#include <stdio.h>
#include <stdlib.h>

void pointer_errors() {
    int* ptr = (int*)malloc(sizeof(int));
    *ptr = 42;

    // Error 1: Dereferencing NULL pointer
    int* null_ptr = NULL;
    printf("%d\n", *null_ptr);  // Crash!

    // Error 2: Returning pointer to local variable
    int local_var = 100;
    return &local_var;  // Dangling pointer!
}

Solutions

// Good: Safe pointer usage
#include <stdio.h>
#include <stdlib.h>

int* safe_pointer_usage() {
    int* ptr = (int*)malloc(sizeof(int));
    if (ptr == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return NULL;
    }

    *ptr = 42;

    // Check pointer before dereferencing
    int* null_ptr = NULL;
    if (null_ptr != NULL) {
        printf("%d\n", *null_ptr);
    }

    return ptr;  // Return allocated memory, not local variable address
}

// Better: Use static allocation for returning pointers
int* get_static_pointer() {
    static int static_var = 100;
    return &static_var;  // Safe: static variable exists after function returns
}

🔧 Logic Errors

6. Off-by-One Errors

Incorrect loop boundaries or array indexing.

Problem

// Bad: Off-by-one error
void off_by_one_error() {
    int arr[5] = {1, 2, 3, 4, 5};

    // Process first 5 elements
    for (int i = 0; i <= 5; i++) {  // Should be i < 5
        printf("%d\n", arr[i]);  // Accesses arr[5]!
    }
}

Solutions

// Good: Correct loop bounds
void correct_loop_bounds() {
    int arr[5] = {1, 2, 3, 4, 5};

    // Process first 5 elements
    for (int i = 0; i < 5; i++) {  // Correct bounds
        printf("%d\n", arr[i]);
    }
}

// Better: Use sizeof for array size
void better_loop_bounds() {
    int arr[5] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);

    for (int i = 0; i < size; i++) {
        printf("%d\n", arr[i]);
    }
}

7. Infinite Loops

Loops that never terminate due to incorrect conditions.

Problem

// Bad: Infinite loop
void infinite_loop() {
    int i = 0;
    while (i < 10) {
        printf("%d\n", i);
        // Forgot i++ - infinite loop!
    }
}

Solutions

// Good: Ensure loop termination
void correct_loop() {
    int i = 0;
    while (i < 10) {
        printf("%d\n", i);
        i++;  // Critical!
    }
}

// Better: Use for-loop when possible
void better_loop() {
    for (int i = 0; i < 10; i++) {
        printf("%d\n", i);
    }
}

8. Incorrect Format Specifiers

Using wrong printf format specifiers.

Problem

// Bad: Format specifier mismatch
#include <stdio.h>

void format_errors() {
    int num = 42;
    double pi = 3.14159;

    printf("%d\n", pi);    // Wrong: %d for double
    printf("%f\n", num);    // Wrong: %f for int
    printf("%s\n", num);    // Wrong: %s for int
}

Solutions

// Good: Correct format specifiers
#include <stdio.h>

void correct_format() {
    int num = 42;
    double pi = 3.14159;

    printf("%d\n", num);    // Correct: %d for int
    printf("%f\n", pi);    // Correct: %f for double
    printf("%d\n", num);    // Correct: %d for int
}

9. Type Conversion Issues

Implicit type conversions causing unexpected behavior.

Problem

// Bad: Implicit conversion issues
#include <stdio.h>

void type_conversion_errors() {
    int a = 5;
    int b = 2;
    double result = a / b;  // Integer division, then convert to double
    printf("%f\n", result);  // Prints 2.000000, not 2.500000
}

Solutions

// Good: Explicit type conversion
#include <stdio.h>

void correct_type_conversion() {
    int a = 5;
    int b = 2;
    double result = (double)a / b;  // Explicit conversion
    printf("%f\n", result);  // Prints 2.500000
}

// Better: Use appropriate literals
void better_type_conversion() {
    int a = 5;
    int b = 2;
    double result = a / 2.0;  // Double literal forces floating point division
    printf("%f\n", result);  // Prints 2.500000
}

🛠️ Debugging Tips

Common Debugging Strategies

  1. Use Compiler Warnings: Enable all warnings with -Wall -Wextra
  2. Use Debugger: GDB for runtime debugging
  3. Memory Tools: Valgrind for memory issues
  4. Static Analysis: Tools like cppcheck for code analysis
  5. Assert Usage: Use assertions for development-time checks

Debugging Checklist

  • Initialize all variables
  • Check array bounds before access
  • Validate pointers before dereferencing
  • Check malloc return values
  • Free all allocated memory
  • Use correct format specifiers
  • Check for integer overflow
  • Ensure loop termination

Using GDB Effectively

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

# Common GDB commands
(gdb) break main          # Set breakpoint
(gdb) run               # Start program
(gdb) next              # Next line
(gdb) step              # Step into function
(gdb) print variable     # Print variable value
(gdb) continue          # Continue execution
(gdb) bt                # Show backtrace
(gdb) quit              # Exit debugger

Memory Debugging with Valgrind

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

# Run with valgrind
valgrind --leak-check=full --show-leak-kinds=all ./program

# Common valgrind options
--leak-check=full     # Detailed leak information
--show-leak-kinds=all  # Show all leak types
--track-origins=yes    # Track where uninitialized values come from

🔗 Common Programming Mistakes