free() inconsistencies|inconsistências no free()

Last week I was porting a program from uClibc to glibc. Everything went fine, until I found a crash to always happen in a certain part of it. The failure was in a call to the free() function. First thing that came in my head: Why the hell is it crashing now, while it used to run fine on uClibc? I made a simple program that simulates the problem:

Look at line 16. I’m executing a free() in a pointer to a static variable, instead of a pointer in the heap (previously allocated with malloc() or similar). It’s expected a crash here, right? Maybe! Yes, if you’re using glibc. No if you’re using uClibc. The above code works like [not] expected. Weird! Everything we learned at the programming school is ruined now !
So, we have a similar code here that have been worked for a long time, exactly because it was compiled and run on top of uClibc. I’ve seen this and other behavior differences between uClibc and glibc. The solution? Change the code to make it portable, not only to make it compile, but also so that it have the same behavior on every platform.

I thought it was a bug in uClibc, but I was told it doesn’t break the standards. In fact, standards say, in that case, the behavior is “undefined”. Ah, standards … So, in order to avoid surprises like that, here is what I learned: Always code in the right way, even if it comes with a harder job. Don’t say: “hey, it’s working, let’s deploy it!”.

4 comments ↓

Microsoft’s Raymond Chen has had much to say on that subject over the years – most of which amounts to that if the API says behavior is officially undefined under a particular situation, don’t rely on the observed behavior being consistent. It may change with different implementations of the API, it may change when bug fixes go in, it may even change based on the phase of the moon.

The C standard has three notions of underspecification; unspecified behavior, implementation defined behavior and undefined behavior. The interesting thing about undefined behavior (which indeed occurs in your example) is that an implementation is basically allowed to do whatever it likes for the remaining of the execution of your program. So it is even allowed to format your hard disk or to let your computer explode.

Your example is not very interesting though, since the C99 clearly states that

Otherwise, if the argument does not match a
pointer earlier returned by the calloc, malloc, or
realloc function, or if the space has been
deallocated by a call to free or realloc, the
behavior is undefined.

Hence it is not an inconsistency. Now let us look at real inconsistencies with respect to free in the C99 standard. Consider

Here it is very likely that the second malloc call yields a pointer to the same piece of memory as the first. After that we check whether the bit representations of the pointers p (that has been freed, and has an indeterminate value according to the standard) and q (that is not freed) are equal. In this case one might wonder whether we could use p and q interchangeably, after all, their bit representations are equal (and bits is all there is). But on the other hand, p has an indeterminate value, so dereferencing would be undefined behavior.

To make it even more fun, clang -O3 (version 2.7-3) prints 10 twice in the following example!