This code gives me a segfault sometimes or invalid result on GCC/Clang. However, it works fine on MSVC & ICC. I'm not sure if my code is invalid from the eyes of the standard or if it is a compiler bug in GCC/Clang.
#include <cstdio>
struct A {
virtual void func() {}
void* junk = nullptr;
};
template<typename T>
struct B {
B() {
T* cp = static_cast<T*>(this);
A* a = cp;
printf("%p\n", a);
}
};
struct C : virtual A, B<C>
{
C() : A(), B<C>() {}
};
int main() {
C c;
A* ptr = &c;
printf("%p\n", ptr);
return 0;
}
One such incorrect result on Godbolt (Clang). Both pointers are a result of cast to same base A and as such should give same address:
ASM generation compiler returned: 0
Execution build compiler returned: 0
Program returned: 0
0x7fff9fa82608
0x7fff9fa82510
I have debugged it to understand the root cause behind it. The static_cast<T*>(this) is fine. But the pointer conversion from T* -> A* requires the vtable to get the offset of the base class since A it is a virtual base class. You can see this if you compile this function on godbolt:
A& func(C& c) {
return c;
}
Generated code:
func(C&):
mov rax, rdi % rax = address of input C object
mov rcx, qword ptr [rdi] % rcx = load vtable of C
add rax, qword ptr [rcx - 24] % rax += offset of virtual base class A
ret
However, in the constructor of B, the vtable of the *this object is not set to the vtable of class C. Thus when it gets the offset of virtual base class A from the vtable, it reads some junk value and calculates an invalid address for a.
I'm well aware, I could also modify the constructor of B to: B(A* a) {} and that would work. However, this option is not possible for me as this example is a reduced version of a larger system.
I added another virtual class to C and it still works correctly in ICC and MSVC: Compiler explorer