C Common Mistakes¶
This guide covers common C programming mistakes and how to avoid them, with practical solutions and examples.
🚨 Most Common C Errors¶
1. Memory Leaks¶
Forgetting to free allocated memory or using freed memory.
Problem¶
// Bad: Memory leak
#include <stdlib.h>
#include <stdio.h>
void memory_leak_example() {
int* ptr = (int*)malloc(sizeof(int) * 100);
ptr[0] = 42;
// Forgot to free ptr!
}
void use_freed_memory() {
int* ptr = (int*)malloc(sizeof(int));
*ptr = 42;
free(ptr);
printf("%d\n", *ptr); // Using freed memory!
}
Solutions¶
// Good: Proper memory management
#include <stdlib.h>
#include <stdio.h>
void proper_memory_management() {
int* ptr = (int*)malloc(sizeof(int) * 100);
if (ptr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return;
}
ptr[0] = 42;
printf("%d\n", ptr[0]);
free(ptr); // Always free allocated memory
ptr = NULL; // Avoid dangling pointers
}
// Better: Use RAII-style pattern
typedef struct {
int* data;
size_t size;
} SafeArray;
SafeArray* create_safe_array(size_t size) {
SafeArray* arr = (SafeArray*)malloc(sizeof(SafeArray));
if (arr == NULL) return NULL;
arr->data = (int*)malloc(size * sizeof(int));
if (arr->data == NULL) {
free(arr);
return NULL;
}
arr->size = size;
return arr;
}
void destroy_safe_array(SafeArray* arr) {
if (arr != NULL) {
free(arr->data);
free(arr);
}
}
2. Buffer Overflow¶
Writing beyond array bounds or using unsafe string functions.
Problem¶
// Bad: Buffer overflow vulnerability
#include <string.h>
#include <stdio.h>
void buffer_overflow() {
char buffer[10];
strcpy(buffer, "This string is too long"); // Overflow!
printf("%s\n", buffer);
}
// Bad: Array bounds violation
void array_bounds_violation() {
int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i <= 5; i++) { // Should be i < 5
printf("%d\n", arr[i]); // Reads beyond array!
}
}
Solutions¶
// Good: Safe string operations
#include <string.h>
#include <stdio.h>
void safe_string_operations() {
char buffer[10];
strncpy(buffer, "This string is too long", sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0'; // Ensure null termination
printf("%s\n", buffer);
}
// Good: Proper array bounds
void safe_array_access() {
int arr[5] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < size; i++) { // Correct bounds
printf("%d\n", arr[i]);
}
}
3. Uninitialized Variables¶
Using variables before initialization leads to undefined behavior.
Problem¶
// Bad: Uninitialized variables
int uninitialized_example() {
int x, y, z;
printf("%d\n", x + y + z); // Undefined behavior!
int* ptr;
*ptr = 42; // Writing to uninitialized pointer!
}
Solutions¶
// Good: Always initialize variables
int initialized_example() {
int x = 0, y = 0, z = 0;
printf("%d\n", x + y + z);
int* ptr = NULL;
if (ptr != NULL) {
*ptr = 42;
}
}
// Better: Use compiler warnings
// Compile with: gcc -Wall -Wextra -Werror
4. Integer Overflow¶
Arithmetic operations exceeding the range of data types.
Problem¶
// Bad: Integer overflow
#include <limits.h>
#include <stdio.h>
void integer_overflow() {
int max_int = INT_MAX;
int result = max_int + 1; // Overflow!
printf("%d\n", result); // Undefined behavior
}
Solutions¶
// Good: Check for overflow
#include <limits.h>
#include <stdio.h>
void safe_integer_operations() {
int max_int = INT_MAX;
int result;
// Check before addition
if (max_int > INT_MAX - 1) {
printf("Would overflow\n");
result = INT_MAX;
} else {
result = max_int + 1;
}
printf("%d\n", result);
}
// Better: Use larger data types
void safe_with_larger_type() {
long long max_int = INT_MAX;
long long result = max_int + 1; // No overflow
printf("%lld\n", result);
}
5. Pointer Errors¶
Incorrect pointer usage leading to crashes or undefined behavior.
Problem¶
// Bad: Pointer errors
#include <stdio.h>
#include <stdlib.h>
void pointer_errors() {
int* ptr = (int*)malloc(sizeof(int));
*ptr = 42;
// Error 1: Dereferencing NULL pointer
int* null_ptr = NULL;
printf("%d\n", *null_ptr); // Crash!
// Error 2: Returning pointer to local variable
int local_var = 100;
return &local_var; // Dangling pointer!
}
Solutions¶
// Good: Safe pointer usage
#include <stdio.h>
#include <stdlib.h>
int* safe_pointer_usage() {
int* ptr = (int*)malloc(sizeof(int));
if (ptr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return NULL;
}
*ptr = 42;
// Check pointer before dereferencing
int* null_ptr = NULL;
if (null_ptr != NULL) {
printf("%d\n", *null_ptr);
}
return ptr; // Return allocated memory, not local variable address
}
// Better: Use static allocation for returning pointers
int* get_static_pointer() {
static int static_var = 100;
return &static_var; // Safe: static variable exists after function returns
}
🔧 Logic Errors¶
6. Off-by-One Errors¶
Incorrect loop boundaries or array indexing.
Problem¶
// Bad: Off-by-one error
void off_by_one_error() {
int arr[5] = {1, 2, 3, 4, 5};
// Process first 5 elements
for (int i = 0; i <= 5; i++) { // Should be i < 5
printf("%d\n", arr[i]); // Accesses arr[5]!
}
}
Solutions¶
// Good: Correct loop bounds
void correct_loop_bounds() {
int arr[5] = {1, 2, 3, 4, 5};
// Process first 5 elements
for (int i = 0; i < 5; i++) { // Correct bounds
printf("%d\n", arr[i]);
}
}
// Better: Use sizeof for array size
void better_loop_bounds() {
int arr[5] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < size; i++) {
printf("%d\n", arr[i]);
}
}
7. Infinite Loops¶
Loops that never terminate due to incorrect conditions.
Problem¶
// Bad: Infinite loop
void infinite_loop() {
int i = 0;
while (i < 10) {
printf("%d\n", i);
// Forgot i++ - infinite loop!
}
}
Solutions¶
// Good: Ensure loop termination
void correct_loop() {
int i = 0;
while (i < 10) {
printf("%d\n", i);
i++; // Critical!
}
}
// Better: Use for-loop when possible
void better_loop() {
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
}
}
🎯 Type-Related Mistakes¶
8. Incorrect Format Specifiers¶
Using wrong printf format specifiers.
Problem¶
// Bad: Format specifier mismatch
#include <stdio.h>
void format_errors() {
int num = 42;
double pi = 3.14159;
printf("%d\n", pi); // Wrong: %d for double
printf("%f\n", num); // Wrong: %f for int
printf("%s\n", num); // Wrong: %s for int
}
Solutions¶
// Good: Correct format specifiers
#include <stdio.h>
void correct_format() {
int num = 42;
double pi = 3.14159;
printf("%d\n", num); // Correct: %d for int
printf("%f\n", pi); // Correct: %f for double
printf("%d\n", num); // Correct: %d for int
}
9. Type Conversion Issues¶
Implicit type conversions causing unexpected behavior.
Problem¶
// Bad: Implicit conversion issues
#include <stdio.h>
void type_conversion_errors() {
int a = 5;
int b = 2;
double result = a / b; // Integer division, then convert to double
printf("%f\n", result); // Prints 2.000000, not 2.500000
}
Solutions¶
// Good: Explicit type conversion
#include <stdio.h>
void correct_type_conversion() {
int a = 5;
int b = 2;
double result = (double)a / b; // Explicit conversion
printf("%f\n", result); // Prints 2.500000
}
// Better: Use appropriate literals
void better_type_conversion() {
int a = 5;
int b = 2;
double result = a / 2.0; // Double literal forces floating point division
printf("%f\n", result); // Prints 2.500000
}
🛠️ Debugging Tips¶
Common Debugging Strategies¶
- Use Compiler Warnings: Enable all warnings with
-Wall -Wextra - Use Debugger: GDB for runtime debugging
- Memory Tools: Valgrind for memory issues
- Static Analysis: Tools like cppcheck for code analysis
- Assert Usage: Use assertions for development-time checks
Debugging Checklist¶
- Initialize all variables
- Check array bounds before access
- Validate pointers before dereferencing
- Check malloc return values
- Free all allocated memory
- Use correct format specifiers
- Check for integer overflow
- Ensure loop termination
Using GDB Effectively¶
# Compile with debug symbols
gcc -g program.c -o program
# Common GDB commands
(gdb) break main # Set breakpoint
(gdb) run # Start program
(gdb) next # Next line
(gdb) step # Step into function
(gdb) print variable # Print variable value
(gdb) continue # Continue execution
(gdb) bt # Show backtrace
(gdb) quit # Exit debugger
Memory Debugging with Valgrind¶
# Compile with debug symbols
gcc -g program.c -o program
# Run with valgrind
valgrind --leak-check=full --show-leak-kinds=all ./program
# Common valgrind options
--leak-check=full # Detailed leak information
--show-leak-kinds=all # Show all leak types
--track-origins=yes # Track where uninitialized values come from
📚 Related Resources¶
- C Best Practices - Prevent these mistakes with good practices
- C Performance Tips - Optimize your C code
- C Debugging Techniques - Advanced debugging strategies
- C Resources - Learning materials and tools
🔗 Common Programming Mistakes¶
- Logic Errors - General logic mistakes
- Runtime Errors - Common runtime issues
- Syntax Errors - Syntax and compilation errors