ANSI C is one of the oldest and most influential programming languages of all time. Despite its efficiency and closeness to the hardware, it harbors numerous pitfalls that can surprise even experienced developers time and again. In this article you will find a comprehensive collection of typical quirks, stumbling blocks, and best practices in C. The goal is to give you a deeper understanding of the language, avoid sources of errors, and write portable, clean code – especially in low-level programming.
Many problems arise from implicit type conversions, undefined behavior, or compiler-dependent implementations. Knowing these peculiarities allows you not only to avoid bugs, but also to make targeted use of the full power of C.
1. Multiple Dereferencing of Function Pointers
2. Sign Handling of Constants (signed vs. unsigned)
3. Order of Evaluation of Function Arguments
4. Parentheses with Pointers and Increment/Decrement Operators
5. Risk of Errors in Type Conversions
Function pointers are a powerful but also error-prone tool in C. Confusion can quickly arise, especially with multiple dereferencing:
void func(void) { printf("Hello\n"); } void (*fp)(void) = func;
void (**fpp)(void) = &fp; (*fpp)(); // correct (**fpp)(); // also correct
Both calls work, since function pointers can be automatically dereferenced. Nevertheless, readability suffers greatly. Recommendation: Avoid unnecessarily complex pointer constructions.
What output does the following code produce?
unsigned int a = 1;
if (a > -1) printf("1 > -1");
else printf("1 <= -1");
The result is surprising: 1 <= -1
Reason: With mixed types, -1 is converted to unsigned int. This produces the largest possible positive number – and that is of course greater than 1.
Note: Mixing signed and unsigned frequently leads to unexpected behavior.
unsigned are usually computed in unsigned
The order in which function arguments are evaluated is not defined in C:
int i = 1; printf("%d %d\n", i, i++);
The result is undefined and may vary depending on the compiler.
Recommendation: Avoid side effects within function arguments.
Small differences in parenthesization have major effects:
char *lp = line; ch = *lp++; // equivalent to *(lp++) ch = (*lp)++; // increments the value, not the pointer
Note: Operator precedences in C are complex. When in doubt, always use parentheses!
The behavior of char is implementation-dependent:
char a = 228; printf("%d\n", a);
Output can be:
Recommendation:
signed char or unsigned char-fsigned-char, -funsigned-char)
A central concept in C is Undefined Behavior. Examples:
int x = 5; x = x++; // undefined
Or:
int a[3] = {1,2,3}; printf("%d\n", a[5]); // access outside the array
Such code can:
A common mistake is incorrect handling of malloc and free:
int *p = malloc(sizeof(int) * 10); free(p); free(p); // error: double free
Other typical problems:
free)free)
-Wall -Wextra
C is an extremely powerful language – but it is precisely this closeness to the hardware that also makes it susceptible to subtle errors. Knowing the typical quirks and pitfalls allows you not only to write more stable code, but also to take full advantage of C.
Do you know any other peculiarities or typical errors in C? Feel free to share them in the comments and expand this collection!