How does C++ visit member function and virtual member function
Preconditions
Before watching this blog, I suggest you already known what is virtual
function and already known some basic stuff about C++ class.
Question
Recently I am reviewing the memory layout of C++ class and I have encountered the following series of problems:
- When we query the size of a class, why size of member functions (not virtual) don’t occupy memory.
- When a class derive from a class that has virtual functions, how many virtual table point does it has.
Answer 1
For the first question, the answer is during the compilation stage, every call to a member function of a class is translated. We know that after a function is compiled, it will be placed into the read-only code segment, so the address of this function won’t change during the ruining time.
In addition, for any member function, it has a hidden parameter corresponding to the instance that calling it:
lea -0x20(%rbp),%rax # put instance into rax, then into rdi, which represent the first parameter
mov %rax,%rdi
call 0x55555555524c <ChildA::test()>
Since we have such nice properties, the compiler simply translate each function call into above format, so we don’t need to store a pointer inside a class to point to its member function.
Answer 2
I thought the answer is it contains two vpointers, the memory layout of child class looks like:
----- child
| Vpointer
| SomethingElse
|----- Parent
|| Vpointer
|| SomethingElse
|-----
-----
The reason is I found that I can still access parent’s virtual function by doing child.Parent::function()
, but it is wrong because this is in fact call though another mechanism rather than using vtable: Instead of finding the function in the vtable, it finds Parent::function()
in the code segment, and pass the instance (derived instance, but it is still acceptable by base class’s function) into the function.
#include <iostream>
using namespace std;
class BaseA {
public:
virtual void F1() {
printf("something A");
};
};
class ChildA : public BaseA{
public:
void F1(){
printf("something B");
}
};
int main() {
ChildA a;
a.BaseA::F1();
a.F1();
}
So the answer to question 2 is only 1 vpointer.