type
status
date
slug
summary
tags
category
icon
password
At the start, let's think about a simple class named
Person
. It has a function PrintType
that prints the string "Person".When this code is compiled, the compiler creates instructions for it in assembly language. Here, it sets up a 'this' pointer and then calls the
Person::PrintType
function.The important thing to note here is the
E8
type call instruction. This instruction uses relative addressing, an approach that calculates the location of the function to be called based on its distance from the current instruction. So, the function address is determined and hardcoded into the code.Now, let's change the
PrintType
function to be a virtual function. This means the function can be redefined in any class that inherits from it. So we now have a base class Object
with virtual functions, and a Person
class that overrides these functions.When we call the
PrintType
function now, the compiler uses a different strategy to find the function.Now, it uses a
FF
type call instruction. This instruction is more complex, and it's used for calling virtual functions. This instruction uses indirect addressing, which means the function address to be called is looked up from the address provided after the FF
code. In this scenario, the function address is looked up from the EAX
register. Guess what is stored in the EAX register at the moment? It's the second virtual function stored in the Person's virtual table.When a class has virtual functions, the compiler adds a virtual table for this class and all its derived classes. This is a list of all the virtual functions that the class has. Each object of a class with virtual functions has a pointer to its virtual table, usually stored as the first member of the object. The virtual table itself is an array where each entry is a function pointer pointing to the most derived function for that object.
virtual table setup
In C++, when a class has one or more virtual functions, the compiler automatically adds a hidden member variable to the class. This variable is a pointer, often called the virtual pointer, which points to a static array of function pointers, called the vtable (or virtual table). Each class with virtual functions has its own vtable, and all objects of that class have a virtual pointer that point to that virtual table.
Now, in the case of a constructor, the compiler generates assembly code that, among other things, initializes this vptr. The vptr is typically initialized at the beginning of the constructor, before any user code is executed but after the initialization of its base class, to ensure that the vtable is correctly set up even if a virtual function is called during the constructor.
Note that in the case of multiple inheritance, the derived class would have multiple virtual tables, one for each of its base class.
virtual table usage
When calling virtual functions, the vtable is utilized. Take
Person::PrintType
as an example, here's an explanation of the assembly code for the call instruction:mov eax, dword ptr [p]
: This moves the address of thePerson
object into theeax
register.
mov edx, dword ptr [eax]
: Pass the value of the virtual pointer ofp
, which is the memory address of its virtual table, intoedx
register.
mov ecx, dword ptr [p]
: Setupthis
pointer for function call.
mov eax, dword ptr [edx + 4]
: The second function pointer from the vtable (whichedx
points to) is moved to theeax
register. In this case, this should be the address of thePerson::PrintType
function.
call eax
: This calls the function whose address is stored ineax
, which should bePerson::PrintType
.
- 作者:Zack Yang
- 链接:https://zackyang.blog/article/vtable-impl-details
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章