I ran into a weird C++ code behaviour, not sure whether it's a compiler bug or simply undefined/unspecified behaviour of my code. Here is the code:
#include <unistd.h>
#include <iostream>
#include <thread>
struct Parent {
std::thread t;
static void entry(Parent* p) {
p->init();
p->fini();
}
virtual ~Parent() { t.join(); }
void start() { t = std::thread{entry, this}; }
virtual void init() { std::cout << "Parent::init()" << std::endl; }
virtual void fini() { std::cout << "Parent::fini()" << std::endl; }
};
struct Child : public Parent {
virtual void init() override { std::cout << "Child::init()" << std::endl; }
virtual void fini() override { std::cout << "Child::fini()" << std::endl; }
};
int main() {
Child c;
c.start();
sleep(1); // <========== here is it
return 0;
}
The output of the code would be the following, which isn't surprising:
Child::init()
Child::fini()
However, if the function call "sleep(1)" is commented out, the output would be:
Parent::init()
Parent::~fini()
Tested on Ubuntu 15.04, both gcc-4.9.2 and clang-3.6.0 show the same behaviour. Compiler options:
g++/clang++ test.cpp -std=c++11 -pthread
It looks like a race condition (the vtable not fully constructed before the thread starts). Is this code ill-formed ? a compiler bug ? or it's supposed to be like this ?
@KerrekSB commented:
” The thread uses the child object, but the child object is destroyed before the thread is joined (because the joining only happens after the destruction of the child has begun).
The Child
object is destroyed at the end of main
. The Child
destructor is executed, and effectively calls the Parent
destructor, where Parent
bases (no such) and data members (the thread object) are destroyed. As destructors are invoked up the chain of base classes the dynamic type of the object changes, in reverse order of how it changes during construction, so at this point the type of the object is Parent
.
The virtual calls in the thread function can happen before, overlapping with or after the call of the Child
destructor, and in the case of overlapping there's one thread accessing storage (in practice, the vtable pointer) that is being changed by another thread. So this is Undefined Behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With