C Programming: Common Mistakes & Pitfalls


๐จ Memory Management Pitfalls
1. Memory Leaks - Forgetting to Free
โ WRONG:
void bad_function() {
int* ptr = malloc(100 * sizeof(int));
// Do some work
return; // MEMORY LEAK! Never freed ptr
}
โ CORRECT:
void good_function() {
int* ptr = malloc(100 * sizeof(int));
if (ptr == NULL) {
return; // Handle allocation failure
}
// Do some work
free(ptr);
ptr = NULL; // Prevent dangling pointer
}
2. Double Free - Freeing Same Memory Twice
โ WRONG:
int* ptr = malloc(sizeof(int));
free(ptr);
free(ptr); // UNDEFINED BEHAVIOR! Double free
โ CORRECT:
int* ptr = malloc(sizeof(int));
free(ptr);
ptr = NULL; // Set to NULL after freeing
if (ptr != NULL) {
free(ptr); // Safe - won't execute
}
3. Dangling Pointers - Using Freed Memory
โ WRONG:
int* ptr = malloc(sizeof(int));
*ptr = 42;
free(ptr);
printf("%d\n", *ptr); // UNDEFINED BEHAVIOR! Using freed memory
โ CORRECT:
int* ptr = malloc(sizeof(int));
*ptr = 42;
printf("%d\n", *ptr); // Use before freeing
free(ptr);
ptr = NULL; // Prevent future use
4. Buffer Overflow - Writing Beyond Array Bounds
โ WRONG:
char buffer[10];
strcpy(buffer, "This string is too long!"); // BUFFER OVERFLOW!
int arr[5];
for (int i = 0; i <= 5; i++) { // Off-by-one error
arr[i] = i; // Writing to arr[5] is out of bounds!
}
โ CORRECT:
char buffer[30]; // Ensure buffer is large enough
strcpy(buffer, "This string fits!");
// Or use safe functions
char buffer[10];
strncpy(buffer, "Safe copy", sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0'; // Ensure null termination
int arr[5];
for (int i = 0; i < 5; i++) { // Use < not <=
arr[i] = i;
}
๐ฏ Pointer Mistakes
5. Uninitialized Pointers
โ WRONG:
int* ptr; // Uninitialized pointer
*ptr = 42; // UNDEFINED BEHAVIOR! Points to random memory
โ CORRECT:
int* ptr = NULL; // Initialize to NULL
int value = 42;
ptr = &value; // Point to valid memory
*ptr = 42; // Safe to use
6. Pointer Arithmetic Errors
โ WRONG:
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
ptr += 10; // Points way beyond array
printf("%d\n", *ptr); // UNDEFINED BEHAVIOR!
โ CORRECT:
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
for (int i = 0; i < 5; i++) {
printf("%d\n", ptr[i]); // Safe array access
}
7. Returning Pointer to Local Variable
โ WRONG:
int* bad_function() {
int local_var = 42;
return &local_var; // DANGLING POINTER! local_var destroyed
}
โ CORRECT:
int* good_function() {
int* ptr = malloc(sizeof(int));
if (ptr == NULL) return NULL;
*ptr = 42;
return ptr; // Caller must free this
}
// Or use static
int* another_good_function() {
static int static_var = 42; // Lives beyond function
return &static_var;
}
๐ String Handling Traps
8. Missing Null Terminator
โ WRONG:
char str[5] = {'H', 'e', 'l', 'l', 'o'}; // No null terminator!
printf("%s\n", str); // UNDEFINED BEHAVIOR!
โ CORRECT:
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // Include null terminator
// OR
char str[] = "Hello"; // Automatically null-terminated
9. strcpy vs strncpy Confusion
โ WRONG:
char dest[5];
strcpy(dest, "This is too long"); // BUFFER OVERFLOW!
char dest2[10];
strncpy(dest2, "Hello", 10);
printf("%s\n", dest2); // May not be null-terminated!
โ CORRECT:
char dest[20]; // Ensure destination is large enough
strcpy(dest, "Hello");
// OR use safe version
char dest2[10];
strncpy(dest2, "Hello World", sizeof(dest2) - 1);
dest2[sizeof(dest2) - 1] = '\0'; // Ensure null termination
10. String Literals Are Read-Only
โ WRONG:
char* str = "Hello";
str[0] = 'h'; // UNDEFINED BEHAVIOR! Modifying string literal
โ CORRECT:
char str[] = "Hello"; // Creates modifiable copy
str[0] = 'h'; // Safe to modify
// OR
char* str = malloc(6);
strcpy(str, "Hello");
str[0] = 'h'; // Safe to modify
free(str);
๐ Operator & Logic Mistakes
11. Assignment vs Comparison
โ WRONG:
int x = 5;
if (x = 3) { // ASSIGNMENT, not comparison!
printf("This always executes\n");
}
// x is now 3, not 5!
โ CORRECT:
int x = 5;
if (x == 3) { // COMPARISON
printf("This won't execute\n");
}
// x is still 5
// Defensive programming: put constant first
if (3 == x) { // Compiler error if you use =
printf("This won't execute\n");
}
12. Operator Precedence Confusion
โ WRONG:
int x = 5;
if (x & 1 == 0) { // Wrong! This is x & (1 == 0), not (x & 1) == 0
printf("Even\n");
}
โ CORRECT:
int x = 5;
if ((x & 1) == 0) { // Use parentheses for clarity
printf("Even\n");
}
13. Logical vs Bitwise Operators
โ WRONG:
int a = 5, b = 3;
if (a & b) { // Bitwise AND, not logical AND
printf("This might not work as expected\n");
}
โ CORRECT:
int a = 5, b = 3;
if (a && b) { // Logical AND
printf("Both are non-zero\n");
}
// Use bitwise only when you mean it
if ((a & b) != 0) { // Explicit bitwise check
printf("They share some bits\n");
}
๐ Array & Function Pitfalls
14. Array Parameter Decay
โ WRONG:
void bad_function(int arr[]) {
int size = sizeof(arr) / sizeof(arr[0]); // WRONG! arr is a pointer
printf("Size: %d\n", size); // Will print pointer size, not array size
}
โ CORRECT:
void good_function(int arr[], int size) {
// Pass size separately
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
// Or use macros for fixed-size arrays
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = ARRAY_SIZE(numbers); // Works in same scope
good_function(numbers, size);
}
15. Scanf Buffer Problems
โ WRONG:
char name[10];
scanf("%s", name); // BUFFER OVERFLOW if input > 9 chars!
int age;
scanf("%d", age); // WRONG! Missing &
โ CORRECT:
char name[10];
scanf("%9s", name); // Limit input to 9 chars + null terminator
int age;
scanf("%d", &age); // Use address-of operator
// Even better - use fgets for strings
fgets(name, sizeof(name), stdin);
๐ง Macro & Preprocessor Traps
16. Macro Side Effects
โ WRONG:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int x = 5;
int result = MAX(++x, 10); // x incremented TWICE!
โ CORRECT:
// Use inline functions instead (C99+)
inline int max(int a, int b) {
return (a > b) ? a : b;
}
int x = 5;
int result = max(++x, 10); // x incremented only once
17. Missing Parentheses in Macros
โ WRONG:
#define SQUARE(x) x * x
int result = SQUARE(2 + 3); // Expands to 2 + 3 * 2 + 3 = 11, not 25!
โ CORRECT:
#define SQUARE(x) ((x) * (x))
int result = SQUARE(2 + 3); // Expands to ((2 + 3) * (2 + 3)) = 25
๐ฒ Type & Casting Issues
18. Integer Overflow
โ WRONG:
int a = 2000000000;
int b = 2000000000;
int sum = a + b; // INTEGER OVERFLOW! Undefined behavior
โ CORRECT:
long long a = 2000000000LL;
long long b = 2000000000LL;
long long sum = a + b; // Safe
// Or check for overflow
int a = 2000000000;
int b = 2000000000;
if (a > INT_MAX - b) {
printf("Overflow would occur!\n");
} else {
int sum = a + b;
}
19. Signed/Unsigned Comparison
โ WRONG:
int signed_val = -1;
unsigned int unsigned_val = 1;
if (signed_val < unsigned_val) { // WRONG! -1 becomes large positive
printf("This won't print!\n");
}
โ CORRECT:
int signed_val = -1;
unsigned int unsigned_val = 1;
if (signed_val < (int)unsigned_val) { // Cast to same type
printf("This will print!\n");
}
20. Floating Point Comparison
โ WRONG:
float a = 0.1 + 0.2;
float b = 0.3;
if (a == b) { // WRONG! Floating point precision issues
printf("Equal\n");
} else {
printf("Not equal\n"); // This will print!
}
โ CORRECT:
#include <math.h>
#define EPSILON 1e-9
float a = 0.1 + 0.2;
float b = 0.3;
if (fabs(a - b) < EPSILON) { // Compare with tolerance
printf("Equal within tolerance\n");
}
๐ Control Flow Gotchas
21. Switch Statement Fall-through
โ WRONG:
int grade = 'B';
switch (grade) {
case 'A':
printf("Excellent\n");
case 'B':
printf("Good\n"); // This prints
case 'C':
printf("Average\n"); // This also prints!
default:
printf("Invalid\n"); // This also prints!
}
โ CORRECT:
int grade = 'B';
switch (grade) {
case 'A':
printf("Excellent\n");
break;
case 'B':
printf("Good\n");
break;
case 'C':
printf("Average\n");
break;
default:
printf("Invalid\n");
break;
}
22. Infinite Loops with Unsigned
โ WRONG:
unsigned int i;
for (i = 10; i >= 0; i--) { // INFINITE LOOP!
printf("%u\n", i); // i wraps to UINT_MAX when going below 0
}
โ CORRECT:
int i; // Use signed int
for (i = 10; i >= 0; i--) {
printf("%d\n", i);
}
// OR for unsigned, use different approach
unsigned int i;
for (i = 10; i != UINT_MAX; i--) {
printf("%u\n", i);
if (i == 0) break; // Explicit break at 0
}
๐ก๏ธ Best Practices to Avoid Mistakes
Essential Defensive Programming
// 1. Always check malloc return value
int* ptr = malloc(size * sizeof(int));
if (ptr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return -1;
}
// 2. Initialize variables
int count = 0; // Not: int count;
char* str = NULL; // Not: char* str;
// 3. Use const for read-only parameters
void print_array(const int* arr, int size) {
// arr cannot be modified
}
// 4. Validate function parameters
int divide(int a, int b) {
if (b == 0) {
fprintf(stderr, "Division by zero\n");
return -1;
}
return a / b;
}
// 5. Use static analysis tools
// Compile with: gcc -Wall -Wextra -Werror file.c
Memory Safety Checklist
// โ
Always pair malloc/free
void* ptr = malloc(size);
// ... use ptr
free(ptr);
ptr = NULL;
// โ
Check bounds
for (int i = 0; i < array_size; i++) { // Not <=
// access array[i]
}
// โ
Use safe string functions
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
// โ
Initialize arrays
int arr[10] = {0}; // All elements set to 0
Compiler Flags for Safety
# Essential compiler flags
gcc -Wall -Wextra -Werror -pedantic -std=c99 \
-fsanitize=address -fsanitize=undefined \
-g -O0 program.c -o program
# -Wall -Wextra: Enable most warnings
# -Werror: Treat warnings as errors
# -fsanitize: Runtime error detection
# -g: Debug symbols
# -O0: No optimization (for debugging)
๐ Debugging Tools & Techniques
Valgrind (Linux/Mac)
# Check for memory leaks and errors
valgrind --leak-check=full --show-leak-kinds=all ./program
# Check for uninitialized variables
valgrind --tool=memcheck --track-origins=yes ./program
GDB Debugging
# Compile with debug symbols
gcc -g -O0 program.c -o program
# Debug with gdb
gdb ./program
(gdb) run
(gdb) bt # Backtrace on crash
(gdb) print var # Print variable value
(gdb) step # Step through code
Static Analysis
# Cppcheck
cppcheck --enable=all program.c
# Clang static analyzer
clang --analyze program.c
๐ฏ Quick Reference: Most Critical Rules
- Always initialize pointers to NULL
- Check malloc return values
- Free every malloc'd pointer exactly once
- Set pointers to NULL after freeing
- Never return addresses of local variables
- Use bounds checking for arrays
- Null-terminate strings properly
- Use parentheses in complex expressions
- Be careful with assignment vs comparison
- Compile with all warnings enabled
Remember: C gives you power but no safety net. These mistakes can cause crashes, security vulnerabilities, and hours of debugging. When in doubt, be defensive and explicit!
Subscribe to my newsletter
Read articles from Anni Huang directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Anni Huang
Anni Huang
I am Anni HUANG, a software engineer with 3 years of experience in IDE development and Chatbot.