Keywords: C programming | memory management | debugging techniques
Abstract: This article provides an in-depth analysis of the common "double free or corruption (!prev)" error in C programs. Through a practical case study, it explores issues related to memory allocation, array bounds violations, and uninitialized variables. The paper explains common pitfalls in malloc usage, including incorrect size calculations and improper loop boundary handling, and offers methods for memory debugging using tools like Valgrind. With reorganized code examples and step-by-step explanations, it helps readers understand how to avoid such memory management errors and improve program stability.
Overview of Memory Errors
In C programming, memory management is one of the core challenges developers must face. The "double free or corruption (!prev)" error typically indicates that the program has detected an abnormal condition when freeing memory. This may be due to double-freeing the same memory block, memory corruption, or inconsistencies in the heap structure. When glibc's memory allocator detects such issues, it outputs detailed error information to help locate the problem.
Case Study
Consider the following problematic code snippet:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define TIME 255
#define HARM 32
int main(void) {
double *ptr = malloc(sizeof(double *) * TIME);
if (NULL == ptr) {
printf("ERROR: couldn't allocate waveform memory!\n");
} else {
for(int tcount = 0; tcount <= TIME; tcount++) {
for(int hcount = 0; hcount <= HARM; hcount++) {
double sineRads = ((double)tcount / (double)TIME) * (2*M_PI);
sineRads *= (hcount + 1);
double sine = sin(sineRads);
*(ptr + tcount) += sine;
}
}
free(ptr);
ptr = NULL;
}
return 0;
}
Problem Diagnosis
Through analysis with Valgrind, we can identify several key issues:
Incorrect Memory Allocation Size
Using sizeof(double *) to calculate allocation size is incorrect. On most systems, double * (pointer type) is typically 4 or 8 bytes, while double type is usually 8 bytes. The correct allocation should be:
double *ptr = malloc(sizeof(double) * TIME);
Or a safer approach:
double *ptr = malloc(TIME * sizeof(*ptr));
This ensures that even if the type of ptr changes, the allocation size remains correct.
Array Bounds Violation
The loop condition tcount <= TIME causes array bounds violations. When TIME is defined as 255, the valid array index range is 0 to 254. Using the <= operator causes the loop to execute 256 times, with the last access to ptr[255] exceeding the allocated memory range.
The same issue occurs in the inner loop: hcount <= HARM causes 33 iterations instead of the expected 32. The correct loop conditions should be:
for(int tcount = 0; tcount < TIME; tcount++) {
for(int hcount = 0; hcount < HARM; hcount++) {
// loop body
}
}
Uninitialized Memory Access
When using *(ptr + tcount) += sine, the code does not ensure that ptr[tcount] has been initialized. In C, dynamically allocated memory is not automatically initialized to zero, and directly using the += operation leads to undefined behavior. Initialization should be performed first:
for(int i = 0; i < TIME; i++) {
ptr[i] = 0.0;
}
Code Improvement
Based on the above analysis, the improved code is as follows:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#define TIME 255
#define HARM 32
int main(void) {
// Correct allocation size calculation
double *ptr = malloc(TIME * sizeof(*ptr));
if (ptr == NULL) {
fprintf(stderr, "ERROR: couldn't allocate waveform memory!\n");
return 1;
}
// Initialize memory
memset(ptr, 0, TIME * sizeof(*ptr));
// Correct loop boundaries
for(int tcount = 0; tcount < TIME; tcount++) {
double baseRads = ((double)tcount / (double)TIME) * (2 * M_PI);
for(int hcount = 0; hcount < HARM; hcount++) {
double sineRads = baseRads * (hcount + 1);
ptr[tcount] += sin(sineRads);
}
}
// Free memory
free(ptr);
ptr = NULL;
return 0;
}
Debugging Tool Usage
Valgrind is a powerful tool for detecting memory issues. When encountering "double free or corruption" errors, you should:
- Run the program with
valgrind --leak-check=full ./program - Pay attention to "Invalid read/write" errors, which often point to array bounds violations
- Check for "Conditional jump or move depends on uninitialised value" warnings
- Use the
--track-origins=yesoption to trace the origin of uninitialized values
Alternative Approaches
For arrays of known size that don't require dynamic allocation, consider stack allocation:
double ptr[TIME];
memset(ptr, 0, sizeof(ptr));
This approach avoids the complexity of dynamic memory management but requires attention to stack size limitations.
Conclusion
The "double free or corruption (!prev)" error typically stems from deeper memory management issues. By carefully checking memory allocation sizes, loop boundaries, and variable initialization states, most such errors can be avoided. Using appropriate debugging tools and following good programming practices are key to ensuring memory safety in C programs.