In a TU like this
#include "Foo.hpp"
int main() {
// stuff
Foo* foo{new Foo{}};
foo->foo();
// stuff
}
where Foo.hpp
contains
#pragma once
struct Foo {
virtual void foo(); // implmented somewhere
};
no call to anything other than Foo::foo
can happen, right? foo
is virtual
and nor it nor the class are final
, so yes, in another TU there could be objects of derived classes that override
foo
, and so on, but... as far as this TU is concerned I think it's pretty clear that foo->foo()
calls Foo::foo()
. I don't see how it could be otherwise.
Then why is the generated assembly like this?
main: # @main
push rax
mov edi, 8
call operator new(unsigned long)@PLT
mov rcx, qword ptr [rip + vtable for Foo@GOTPCREL]
add rcx, 16
mov qword ptr [rax], rcx
mov rdi, rax
call Foo::foo()@PLT
xor eax, eax
pop rcx
ret
I don't really understad it in detail, but I clearly read vtable
. Why is it even there?
I'd have expected the assembly of the TU above to be the same as the one I get if I remove the virtual
keyword:
main: # @main
push rax
mov edi, 1
call operator new(unsigned long)@PLT
mov rdi, rax
call Foo::foo()@PLT
xor eax, eax
pop rcx
ret
From this other answer I read that
A* a = new B; a->func();
In this case the compiler can determine that
a
points to aB
object and, thus, call the correct version offunc()
without dynamic dispatch. […] Of course, whether compilers do the corresponding analysis depends on its respective implementation.
Is the answer simply that Clang does not make the analysis that would allow to deduce that no runtime dispatch is needed?
Or am I missing something? Maybe I'm simply totally misunderstanding the assembly?
This line
mov rcx, qword ptr [rip + vtable for Foo@GOTPCREL]
initialises the vtable pointer.
The vtable mechanism is not used to call Foo::foo
, however each object that has a vtable needs to have its vtable pointer initialised. If you don't initialise the vtable pointer of the Foo
object, Foo::foo()
may break (for example, it can try and dynamic_cast
this
).
If you give Foo::foo
an inlinable body, the compiler may (or may not) optimise away the entire Foo
object, depending on what exactly is in the body. However it is unlikely that the compiler will allow an object with uninitialised vtable pointer to exist. It is just too much trouble and not worth the effort to handle this fringe case.