In the following code, apb
and apb->amp
are both pointers, pointing to a malloc address.
The phenomenon is that apb->amp=NULL
will be optimized and will not be executed, but when I remove free(apb)
, the assignment to apb->amp
will not be optimized.
if (apb) {
if (apb->amp) {
free(apb->amp);
apb->amp = NULL;
}
free(apb);
}
I checked the optimization options of gcc and found through experiments that it was caused by the optimization option “-ftree-dse
“:
-ftree-dse
Perform dead store elimination (DSE) on trees. A dead store is a store into a memory location that is later overwritten by another store without any intervening loads. In this case the earlier store can be deleted. This flag is enabled by default at -O1 and higher.
I know that there is a problem with the logic of this code. After apb is released, the address pointed to by apb can no longer be used.
The problem is (I am very curious about this): how does the compiler know the semantics of the free function and optimize it for this kind of situation? Can the compiler also recognize the semantics of other functions and optimize them?
5
how does the compiler know the semantics of the free function and optimize it for this kind of situation? Can the compiler also recognize the semantics of other functions and optimize them?
The free
function has specified semantics in the C standard. The compiler can simply use this to make assumptions about the semantics of this specific function, either by special handling this specific function hard-coded in the compiler or by using some compiler builtin or attribute that is attached to the declaration of free
in the C library to indicate its specific behavior.
Compilers use the specification of functions in the C standard library for special handling of optimizations in other cases as well, whatever cases the compiler developers thought were worth it to implement special handling for.
2
First of all, you have to realize that your code is 100% equivalent to this:
free(apb->amp);
apb->amp = NULL;
free(apb);
The relevant part in the standard is C17 7.22.3.3:
The free function causes the space pointed to by ptr to be deallocated, that is, made available
for further allocation. If ptr is a null pointer, no action occurs.
Basically free(NULL)
is a well-defined no-op.
Next we have to realize that once we call free
with a pointer parameter, the pointed-at object has reached the end of its life time (6.2.4 and 7.22.3) and from there on the contents of that object has become indeterminate. Indeterminate meaning that it can literally have any value and the value need not be consistent from read to read.
As you noted, the apb->amp = NULL;
line is nonsense and poorly-defined, as it writes to an object after freeing it.
But even without the first free(apb->amp);
line, the compiler would still be allowed to optimize away the apb->amp = NULL;
! Because it is still pointless – why write anything there if we are going to free() it directly afterwards anyway? The value of apd
will become indeterminate anyway and you probably have no other references to the memory location of apd->amp
present. Then that memory location is lost to the program.
That’s why you see the optimization happening or not depending on if the final free() is there.
3