Please note that I don't want to solve any problem with my question - I was thinking about probabilities of things to happen and thus was wondering about something:
What exactly happens if you delete on object and use gcc as compiler?
Last week I was investigating a crash, where a race condition lead to an double delete of an object.
The crash occured when calling the virtual destructor of the object, because the pointer to the virtual function table already was overwritten.
Is the virtual function pointer overwritten by the first delete?
If not, is the second delete safe then, as long as no new memory allocation is made in the meantime?
I'm wondering why the problem I had was not recognized before and the only exlanation is that either the virtual function table is overwritten immediatly during the first delete or the second delete does not crash.
(The first means that the crash always occurs on the same location if the "race" happens - the second one, that usually nothing happens when the race happens - and only if a third thread overwrites the delete object in the meantime the problem occurs.)
Edit/Update:
I did a test, the following code crashes with a segfault (gcc 4.4, i686 and amd64):
class M
{
private:
int* ptr;
public:
M() {
ptr = new int[1];
}
virtual ~M() {delete ptr;}
};
int main(int argc, char** argv)
{
M* ptr = new M();
delete ptr;
delete ptr;
}
If I remove the 'virtual' from the dtor, the program is aborted by glibc because it detects the double-free. With the 'virtual' the crash occurs when doing the indirect function call to the destructor, because the pointer to the virtual function table is invalid.
On both amd64 and i686 the pointer points to a valid memory region (heap), but the value there is invalid (a counter? It's very low, e.g 0x11, or 0x21) so the 'call' (or 'jmp' when the compiler did a return-optimization) jumps to an invalid region.
Program received signal SIGSEGV,
Segmentation fault. 0x0000000000000021
in ?? () (gdb)
#
0 0x0000000000000021 in ?? ()
#
1 0x000000000040083e in main ()
So with the above mentioned conditions, the pointer to the virtual function table is ALWAYS overwritten by the first delete, so the next delete will jump to nirvana if the class has a virtual destructor.
It is very dependent on the implementation of the memory allocator itself, not to mention any application dependent failures as overwritting v-table of some object. There are numerous memory allocator schemes all of which differ in capabilities and resistance to double free() but all of these share one common property: your application will crash at some time after the second free().
The reason for the crash is usually that memory allocator dedicates small amount of memory before(header) and after(footer) each allocated chunk of memory to store some implementation specific details. Header usually defines size of the chunk and address of the next chunk. Footer is usually pointer to the header of the chunk. Deleting twice usually at least involves checking if adjacent chunks are free. Thus your program will crash if:
1) pointer to the next chunk has been overwritten and the second free() causes segfault when trying to access the next chunk.
2) the footer of the previous chunk has been modified and access to the header of the previous chunk causes segfault.
If the application survives, it means that free() has either corrupted memory in various locations or will add free chunk which overlaps one of already free chunks, leading to data corruption in the future. Eventually your program will segfault at one of following free() or malloc() involving the corrupted memory areas.