前言
虚函数表对于学习C++的没个人来说都不陌生,面试中也经常问到,但是虚函数表具体是什么东西呢?下面我们就一起来看下
代码
#include <stdio.h>
typedef void (*Func)(void);
class Base
{
public:
Base() {}
virtual ~Base() { printf("%s()\n", __func__); }
virtual void a() { printf("%s()\n", __func__); }
virtual void b() { printf("%s()\n", __func__); }
virtual void c() { printf("%s()\n", __func__); }
int number;
};
int main(int argc, char **argv)
{
Base *base = new Base();
long *vptr = reinterpret_cast<long *>(base); // 虚函数表指针位置
long *virtual_table_address = reinterpret_cast<long *>(*vptr);
Func base_destructure_func_ptr_1 = reinterpret_cast<Func>(*(virtual_table_address + 0));
Func base_destructure_func_ptr_2 = reinterpret_cast<Func>(*(virtual_table_address + 1));
Func base_a_func_ptr = reinterpret_cast<Func>(*(virtual_table_address + 2));
Func base_b_func_ptr = reinterpret_cast<Func>(*(virtual_table_address + 3));
Func base_c_func_ptr = reinterpret_cast<Func>(*(virtual_table_address + 4));
Func unknow_func_ptr_1 = reinterpret_cast<Func>(*(virtual_table_address + 5));
char *typeinfo_name_for_Base = reinterpret_cast<char *>(*(virtual_table_address + 6));
return 0;
}
虚函数表介绍
以下是按照上述代码进行调试
使用GDB查看一下虚函数表第一个函数地址之后的内容
从图中可以看到Base
的析构函数在虚函数表中分成了两个函数
第一个虚析构函数是complete object destructor
会调用该类的非静态数据成员
和非虚拟直接基类
运行析构函数
第二个虚析构函数是deleting destructor
,代码中可直接调用,此函数会调用complete object destructor
和operator delete
也可以在编译时加上-fdump-class-hierarchy
来查看Base的虚函数表
VS Code调试
以下是使用vscode的调试功能进行查看
改变虚析构函数的位置
#include <stdio.h>
typedef void (*Func)(void);
class Base
{
public:
Base() {}
virtual void a() { printf("%s()\n", __func__); }
virtual void b() { printf("%s()\n", __func__); }
virtual void c() { printf("%s()\n", __func__); }
virtual ~Base() { printf("%s()\n", __func__); }
int number;
};
int main(int argc, char **argv)
{
Base *base = new Base();
long *vptr = reinterpret_cast<long *>(base); // 虚函数表指针位置
printf("虚函数表指针的地址: %p\n", vptr);
long *virtual_table_address = reinterpret_cast<long *>(*vptr);
printf("虚函数表的地址: %p\n", virtual_table_address);
Func base_a_func_ptr = reinterpret_cast<Func>(*(virtual_table_address + 0));
printf("虚函数表的第一个函数: %p\n", base_a_func_ptr);
Func base_b_func_ptr = reinterpret_cast<Func>(*(virtual_table_address + 1));
printf("虚函数表的第二个函数: %p\n", base_b_func_ptr);
Func base_c_func_ptr = reinterpret_cast<Func>(*(virtual_table_address + 2));
printf("虚函数表的第三个函数: %p\n", base_c_func_ptr);
Func base_destructure_func_ptr_1 = reinterpret_cast<Func>(*(virtual_table_address + 3));
printf("虚函数表的第四个函数: %p\n", base_destructure_func_ptr_1);
Func base_destructure_func_ptr_2 = reinterpret_cast<Func>(*(virtual_table_address + 4));
printf("虚函数表的第五个函数: %p\n", base_destructure_func_ptr_2);
// base_destructure_func_ptr_2() 此处将会调用Base的析构函数
// 同时会调用base_destructure_func_ptr_1函数
// 如果Base类中存在成员变量时,也会调用operator delete(void*, unsigned long),不存在则不调用
Func unknow_func_ptr_1 = reinterpret_cast<Func>(*(virtual_table_address + 5));
printf("虚函数表的第六个函数: %p\n", unknow_func_ptr_1);
char *typeinfo_name_for_Base = reinterpret_cast<char *>(*(virtual_table_address + 6));
printf("typeid(Base).name() = %s\n", typeinfo_name_for_Base);
return 0;
}
// 以下是base_destructure_func_ptr_2的汇编代码
Base::~Base() [deleting destructor]:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rdi
call Base::~Base() [complete object destructor]
movq -8(%rbp), %rax
movl $16, %esi
movq %rax, %rdi
call operator delete(void*, unsigned long)
leave
ret
从调试结果来看,Base
的虚析构放到了函数c
的后面,由此可得出结论虚函数表是按照虚函数的声明顺序排列的
虚函数表的内存布局
使用Compiler Explorer查看上述代码的汇编