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;