C++虚函数表底层结构
C++ 通过虚函数指针和虚函数表实现多态,但是具体底层是如何实现的呢。可以通过下面这个例子来分析
#include <iostream>
using namespace std;
class base{
virtual void fun1(){ cout << "i am base fun1." << endl; }
virtual void fun2(){ cout << "i am base fun2." << endl; }
};
class other: public base {
virtual void fun1(){ cout << "i am other fun1." << endl; }
virtual void fun3(){ cout << "i am other fun3." << endl; }
};
int main(){
other a;
}
不知道为什么我无法使用gcc -fdump-class-hierarchy test.cpp
命令(也许是新的gcc13.2已经弃用了该命令),我在Windows和Linux下尝试都无法使用该命令,因此我这里使用的是替换的-fdump-lang-class
gcc -dfump-lang-class test.cpp
生成新文件a-test.cpp.001l.class
,打开找到如下片段
·············
Vtable for base
base::_ZTV4base: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI4base)
16 (int (*)(...))base::fun1
24 (int (*)(...))base::fun2
Class base
size=8 align=8
base size=8 base align=8
base (0x0x469b960) 0 nearly-empty
vptr=((& base::_ZTV4base) + 16)
·············
Vtable for other
other::_ZTV5other: 5 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5other)
16 (int (*)(...))other::fun1
24 (int (*)(...))base::fun2
32 (int (*)(...))other::fun3
Class other
size=8 align=8
base size=8 base align=8
other (0x0x46abaf8) 0 nearly-empty
vptr=((& other::_ZTV5other) + 16)
base (0x0x469bc00) 0 nearly-empty
primary-for other (0x0x46abaf8)
可以看到,other类继承了base的虚表结构,同时可以看到other重写了fun1函数、继承了来自base的fun2函数,添加了other自己的fun3函数。
虚函数指针的个数
虚函数指针的个数与继承关系相关,在如上的demo中,每个对象只具有一个虚函数指针,如果是如下的多重继承关系,那么虚函数指针的个数也会发生相应变化。
#include <iostream>
using namespace std;
class base1 {
virtual void fun1() {}
virtual void fun2() {}
};
class base2 {
virtual void fun3() {}
virtual void fun4() {}
};
class other : public base1, public base2 {
virtual void fun1() {}
virtual void fun3() {}
};
int main() {
other a;
return 0;
}
Vtable for other
other::_ZTV5other: 9 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5other)
16 (int (*)(...))other::fun1
24 (int (*)(...))base1::fun2
32 (int (*)(...))other::fun3
40 (int (*)(...))-8
48 (int (*)(...))(& _ZTI5other)
56 (int (*)(...))other::_ZThn8_N5other4fun3Ev
64 (int (*)(...))base2::fun4
Class other
size=16 align=8
base size=16 base align=8
other (0x0x450d5b0) 0
vptr=((& other::_ZTV5other) + 16) // 虚函数指针1
base1 (0x0x4643ba0) 0 nearly-empty
primary-for other (0x0x450d5b0)
base2 (0x0x4643c00) 8 nearly-empty
vptr=((& other::_ZTV5other) + 56) // 虚函数指针2
如上可见,other的对象具有两个虚函数指针。
内存布局
请问 虚函数表
和 虚函数
分别在内存的哪个数据分区?实际上具体的存储位置与编辑器、操作系统相关,经过测试,在不同的系统中,存储位置也不同,我们可以使用 objdump
工具进行分析。
使用上面的测试 demo 在Linux Ubuntu下进行分析:虚函数表在内存的 文字常量区,虚函数在内存的 程序代码区。
- 数据分区。
区域 | 描述 | 变量类型 |
---|---|---|
stack | 栈区 | 临时变量 |
heap | 堆区 | malloc 分配空间的变量 |
.data,.bss | 全局数据区 | 全局变量/静态变量 |
.rodata | 文字常量区 | 只读数据,常量等 |
.text | 程序代码区 | 程序代码 |
- objdump 工具使用。
# 编译测试代码。
g++ -std=c++11 test.cpp -o test
# 使用 objdump 导出执行文件的信息。
objdump -CdStT test > asm.log
# 获取程序代码区信息。
cat asm.log| grep '\.text'
0000000000001130 l F .text 0000000000000000 deregister_tm_clones
0000000000001160 l F .text 0000000000000000 register_tm_clones
00000000000011a0 l F .text 0000000000000000 __do_global_dtors_aux
00000000000011e0 l F .text 0000000000000000 frame_dummy
0000000000001239 l F .text 0000000000000056 __static_initialization_and_destruction_0(int, int)
000000000000128f l F .text 0000000000000019 _GLOBAL__sub_I_main
00000000000012a8 w F .text 000000000000003e other::fun1()
00000000000011e9 g F .text 0000000000000050 main
00000000000012e6 w F .text 000000000000003e other::fun2()
0000000000001324 w F .text 000000000000003e other::fun3()
0000000000001100 g F .text 0000000000000026 _start
# 获取程序文字常量区信息。
cat asm.log| grep '\.data'
0000000000004010 g .data 0000000000000000 _edata
0000000000004000 w .data 0000000000000000 data_start
0000000000004008 g O .data 0000000000000000 .hidden __dso_handle
0000000000003d70 w O .data.rel.ro 0000000000000010 typeinfo for base
0000000000003d58 w O .data.rel.ro 0000000000000018 typeinfo for other
0000000000003d30 w O .data.rel.ro 0000000000000028 vtable for other