Common C Programming Pitfalls and Best Practices

Zero-based array indexing in C often leads to undefined behavior when accessing elements beyond the declared bounds. For instance, declaring int data[30] permits indices from 0 to 29. Accessing data[30] causes memory corruption.

Misplaced break statements in switch constructs can cause unintended control flow. A historical incident in 1990 involved AT&T telephone switches failing due to incorrect break usage. Consider this code:

void process_signal(int signal) {
    switch (signal) {
        case SENSOR_A:
            read_sensor();
            break;
        case SENSOR_B:
            if (status == ACTIVE) {
                process_data();
                break; // Exits the entire switch
            }
            init_pointer(); // Skipped execution
            break;
        default:
            handle_unknown();
    }
    use_pointer(); // Uninitialized pointer reference
}

Octal literals in C start with 0, which may cause confusion:

int decimal_value = 34;
int octal_value = 034; // Equals 28 in decimal

int values[3] = {
    106,  // Decimal 106
    112,  // Decimal 112
    052   // Octal 52 (decimal 42)
};

Pointer arithmetic scales by the data type size. For example:

int *address = (int *)0x1000;
address += 1; // Advances by 4 bytes on 32-bit systems

Incorrect pointer incrementation can corrupt memory:

uint32_t *ram_ptr = start_address;
for (; ram_ptr < end_address; ram_ptr += 4) {
    *ram_ptr = 0;
}

Here, ram_ptr increments by 16 bytes per iteration (4 * sizeof(uint32_t)), leaving 12 bytes uninitialized.

sizeof applied to function parameters returns pointer size, not array length:

void clear_buffer(char buffer[]) {
    for (int i = 0; i < sizeof(buffer) / sizeof(buffer[0]); i++) {
        buffer[i] = 0;
    }
}

In this case, sizeof(buffer) evaluates to 4 on 32-bit systems, clearing only the first four elements.

Prefix and postfix increment/decrement operators behave differently:

int x = 8, y = 2;
int result = x++ + --y; // result = 9, x becomes 9 after assignment

Logical operators && and || short-circuit evaluation:

if (index >= 0 && index++ < max) {
    // index++ executes only if index >= 0
}

Structure padding affects memory layout. These structs have different sizes:

struct {
    char flag;
    short status;
    int value;
} struct1;

struct {
    char flag;
    int value;
    short status;
} struct2;

Typically, struct1 uses 8 bytes while struct2 requires 12 due to alignment padding.

Oeprator precedence errors occur frequantly:

int bcd_result = (bcd_value >> 4) * 10 + bcd_value & 0x0F;

This evaluates incorrect because & has lower precedence than +. Correct version:

int bcd_result = (bcd_value >> 4) * 10 + (bcd_value & 0x0F);

Macro definitions without parentheses cause precedence issues:

#define READ_PIN (PORT & (1 << 11))

if (READ_PIN == (1 << 11)) { ... }

Expands to if (PORT & (1 << 11) == (1 << 11)), where == binds tighter than &, effectively checking bit 0 instead of bit 11.

Implicit type promotions cause subtle bugs:

uint8_t sensor = 0x5A;
uint8_t processed = (~sensor) >> 4;

On 32-bit systems, ~sensor becomes 0xFFFFFFA5, right-shifting yields 0x0FFFFFFA, and truncation results in 0xFA. Correct approach:

uint8_t processed = (uint8_t)(~sensor) >> 4;

Tags: c-programming pointer-arithmetic operator-precedence implicit-conversion struct-padding

Posted on Wed, 09 Sep 2026 16:24:17 +0000 by harrymanjan