Chapter 1

Common C Programming Mistakes and Solutions

Not Initializing Variables

Reason: Uninitialized variables contain random values, leading to unpredictable behavior.
Severity: High. This causes uncertain program behavior and hard-to-debug errors.

Example:

int main() {
    int a; // a is uninitialized
    printf(“%d\n”, a); // prints random value
    return 0;
}

Solution: Always initialize variables.

int main() {
    int a = 0; // initialized to 0
    printf(“%d\n”, a);
    return 0;
}

Improper String Handling

Reason: Strings in C are character arrays. It is easy to forget the \0 terminator, causing out-of-bound access or data corruption.
Severity: High. This leads to data corruption or program crashes.

Example:

int main() {
    char str[5] = “hello”; // out of bounds
    printf(“%s\n”, str);
    return 0;
}

Solution: Ensure the string length matches the array size and handle the terminator correctly.

int main() {
    char str[6] = “hello”; // includes terminator
    printf(“%s\n”, str);
    return 0;
}

Using == to Compare Floating-Point Numbers

Reason: Due to precision issues, floating-point numbers cannot be directly compared with ==.
Severity: Medium. This can lead to incorrect comparison results, affecting program logic.

Example:

int main() {
    float a = 0.1;
    float b = 0.1;
    if (a == b) {
        printf(“Equal\n”);
    } else {
        printf(“Not Equal\n”);
    }
    return 0;
}

Solution: Use a small threshold to determine approximate equality.

#include <math.h>
int main() {
    float a = 0.1;
    float b = 0.1;
    if (fabs(a – b) < 0.00001) {
        printf(“Equal\n”);
    } else {
        printf(“Not Equal\n”);
    }
    return 0;
}

Array Out-of-Bounds

Reason: Accessing an array beyond its boundary leads to undefined behavior or program crashes.
Severity: High. This may cause memory corruption, program crashes, or even security vulnerabilities.

Example:

int main() {
    int arr[3] = {1, 2, 3};
    printf(“%d\n”, arr[3]); // out-of-bounds access
    return 0;
}

Solution: Always access arrays within valid range.

int main() {
    int arr[3] = {1, 2, 3};
    for (int i = 0; i < 3; i++) {
        printf(“%d\n”, arr[i]);
    }
    return 0;
}

Ignoring Data Types and Overflow

Reason: Ignoring data type ranges and overflow can lead to incorrect results.
Severity: Medium. This causes data corruption, logic errors, or security issues.

Example:

int main() {
    unsigned int a = 4294967295; // max value
    a = a + 1; // overflow
    printf(“%u\n”, a); // outputs 0
    return 0;
}

Solution: Handle data types carefully and prevent overflow.

#include <limits.h>
int main() {
    unsigned int a = 4294967295; // max value
    if (a < UINT_MAX) {
        a = a + 1;
    }
    printf(“%u\n”, a);
    return 0;
}

Misuse of Pointers

Reason: Incorrect pointer usage can lead to segmentation faults or memory leaks.
Severity: High. This causes program crashes, memory corruption, or even security vulnerabilities.

Example:

int main() {
    int *p;
    *p = 10; // uninitialized pointer
    printf(“%d\n”, *p);
    return 0;
}

Solution: Always initialize pointers before using them.

int main() {
    int a = 10;
    int *p = &a;
    printf(“%d\n”, *p);
    return 0;
}

Memory Management Mistakes

Reason: Failing to properly free dynamically allocated memory causes memory leaks.
Severity: High. Programs running for a long time may exhaust memory and affect system performance.

Example:

#include <stdlib.h>
int main() {
    int *p = (int*)malloc(sizeof(int) * 5);
    p[0] = 1;
    // forgot to free memory
    return 0;
}

Solution: Always free dynamically allocated memory after use.

#include <stdlib.h>
int main() {
    int *p = (int*)malloc(sizeof(int) * 5);
    p[0] = 1;
    free(p); // free memory
    return 0;
}

Not Checking Function Return Values

Reason: Ignoring function return values can miss error conditions, leading to unexpected results.
Severity: High. This allows the program to continue running despite errors, causing unpredictable outcomes.

Example:

#include <stdio.h>
int main() {
    FILE *fp = fopen(“file.txt”, “r”);
    // did not check if file opened successfully
    return 0;
}

Solution: Always check function return values.

#include <stdio.h>
int main() {
    FILE *fp = fopen(“file.txt”, “r”);
    if (fp == NULL) {
        printf(“Failed to open file\n”);
        return 1;
    }
    // file operations
    fclose(fp);
    return 0;
}

Misuse of Macros

Reason: Macro replacement defects can lead to unexpected behavior.
Severity: Medium. This may cause hard-to-find logic errors.

Example:

#define SQUARE(x) x*x
int main() {
    int a = 3;
    int b = SQUARE(a + 1); // 3+1*3+1 = 7
    printf(“%d\n”, b); // expected 16, got 7
    return 0;
}

Solution: Use parentheses to ensure correct macro expansion.

#define SQUARE(x) ((x)*(x))
int main() {
    int a = 3;
    int b = SQUARE(a + 1); // ((3+1)*(3+1)) = 16
    printf(“%d\n”, b);
    return 0;
}

Ignoring Compiler Warnings

Reason: Compiler warnings often indicate potential problems. Ignoring them may lead to serious errors.
Severity: High. Unresolved warnings may hide critical logic errors or security vulnerabilities.

Example:

int main() {
    int a;
    printf(“%d\n”, a); // uninitialized variable
    return 0;
}

Solution: Pay attention to and fix compiler warnings.

int main() {
    int a = 0; // initialize variable
    printf(“%d\n”, a);
    return 0;
}

By avoiding these common mistakes, beginners can write more reliable and efficient C programs.