Reputation: 1561
I have a new C program written on Ubuntu, compiled with GCC. (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) My code malloc()
’s and free()
’s many, many, many structs on-the-fly. After my program runs for several hours, it crashes with this message:
double free or corruption (fasttop)
Aborted (core dumped)
I’m assuming that “double free” means that my code is calling free()
on a previously-malloc()
ed struct at least twice. That’s very possible. My code creates many different structs depending on the input, and despite my best intentions, it could be trying to free()
something twice.
So I am working with valgrind to find the problem, but another solution occurred to me. Why can’t my code look at a pointer to a struct and know if the struct has been free()
’ed or not? I tried to design a solution. Consider:
typedef struct MeStruct{
int a, b, c;
}me;
me* buildMe( int a, int b, int c ){
me* ret = malloc(sizeof(me));
ret->a = a; ret->b = b; ret->c = c;
return ret;
}
void printMe( me* me1 ){
printf("------------------------------\n");
printf("printMe() :: pointer address is: %p\n", me1);
if( me1 == NULL ){
printf("NULL\n");
} else {
printf( "ME struct :: %d %d %d\n", me1->a, me1->b, me1->c );
}
printf("------------------------------\n");
}
void freeMe( me* me1 ){
printf("Calling freeStuff()\n");
if( me1 != NULL ){
free( me1 );
me1 = NULL; // DOES THIS DO ANYTHING???
}
}
int main( int argc, char **argv ){
me* me1 = buildMe( 1, 2, 3 );
printMe( me1 );
freeMe( me1 ); // First attempt to free( me1 )
printMe( me1 );
freeMe( me1 ); // Second attempt to free( me1 )
printf("END OF PROGRAM.\n");
return 1;
}
So at first, I thought this was pretty clever. When a struct is free()
’ed, the code sets the struct’s pointer to NULL, thus giving a way to detect if the struct is valid or not later. But the line marked with “DOES THIS DO ANYTHIING???
” seems to do nothing. Here’s the output:
------------------------------
printMe() :: pointer address is: 0x555a71097260
ME struct :: 1 2 3
------------------------------
Calling freeStuff()
------------------------------
printMe() :: pointer address is: 0x555a71097260
ME struct :: 0 0 3
------------------------------
Calling freeStuff()
END OF PROGRAM.
So the second call to printMe()
is interesting here. At this point, the struct has been free()
’ed, meaning that the chunk of memory on the heap that was allocated for the struct is no longer allocated. And yet, the struct’s pointer has not changed, and perhaps I can even see some of the original data? (me1->c
is still value “3”)
So it looks like my scheme won’t work, at least not on this compiler. A few questions nag me. First, WHY doesn’t this work? Why isn’t the value of the pointer NULL after the call to freeMe()
? And why didn’t the second call to freeMe()
crash the program? After all, the second call tries to free()
the struct a second time.
Any thoughts or criticism is appreciated, thank you.
EDIT: I fixed a copy/paste error, where the "return 1;" was placed before the "printf("END OF PROGRAM.\n");" call in main()
.
Upvotes: 1
Views: 153
Reputation: 73
A pointer is just a number/memory address. Once you free()
it, it retains its value and is still a pointer, albeit one that no longer points to valid data. That memory it points to may now be used by the memory allocator for subsequent calls to malloc()
.
You may still see stale values from your struct after you've freed that memory because free()
is not obligated to zero out that the freed memory. But you should no longer be accessing it. If you make subsequent calls to malloc()
and populate your heap with more data, you'll likely see your data being overwritten.
As Nate Eldredge explained, C functions pass arguments by value. Your pointer is not being modified for the same reason that v
in the following example remains unchanged after a call to foo()
:
void foo(int a) {
a = 3; // only modifies a in this local context
}
void bar(int *p) {
*p = 3; // modifies what p is pointing to, i.e. v
}
int main(void) {
int v = 1;
foo(v); // v is copied to foo
assert(v == 1); // v remains untouched
bar(&v); // pointer to v is passed to bar
assert(v == 3); // which is successfully modified
return 0;
}
The pointer value you have passed in to freeMe()
is just like the integer value in this example, except it happens to point to some memory address.
Note that a double free induces undefined behavior, so free()
is allowed to do whatever it wants when called upon the same address for the second time --- it doesn't necessarily have to report the double free error.
Upvotes: 4
Reputation: 2422
If you want to assign a permanent NULL
value to the pointer, you need to pass the memory address of the pointer, not the pointer.
void freeMe1(char *temp) {
free(temp); // will free memory; its effect is persistent
temp = NULL; // will assign NULL to pointer but its effect is local only
}
void freeMe2(char **temp) {
free(*temp); // will free memory; its effect is persistent
*temp = NULL; // will assign NULL to pointer and its effect is persistent
}
...
int main {
char *str = malloc(...);
...
...
freeMe1(str); // str is not nullified
freeMe2(&str); // str is nullified
...
}
Upvotes: 2