t2D::allAddOne();
m_z += 1;
}
virtual
int z() const override {
return m_z;
}
private:
int m_z;
};
int main () {
Point3D* p3d = new Point3D(1, 2, 3);
Point2D* p2d = p3d;
p2d->allAddOne();
int z = p2d->z();
}
我们先使用-fdump-class-hierarchy
查看类的信息:
Vtable for Point2D
Point2D::_ZTV7Point2D: 6 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI7Point2D)
16 (int (*)(...))Point2D::~Point2D
24 (int (*)(...))Point2D::~Point2D
32 (int (*)(...))Point2D::allAddOne
40 (int (*)(...))Point2D::z
Class Point2D
size=16 align=8
base size=16 base align=8
Point2D (0x0x7ff517ae7960) 0
vptr=((& Point2D::_ZTV7Point2D) + 16)
Vtable for Point3D
Point3D::_ZTV7Point3D: 16 entries
0 16
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI7Point3D)
24 (int (*)(...))Point3D::~Point3D
32 (int (*)(...))Point3D::~Point3D
40 (int (*)(...))Point3D::allAddOne
48 (int (*)(...))Point3D::z
56 18446744073709551600
64 18446744073709551600
72 18446744073709551600
80 (int (*)(...))-16
88 (int (*)(...))(& _ZTI7Point3D)
96 (int (*)(...))Point3D::_ZTv0_n24_N7Point3DD1Ev
104 (int (*)(...))Point3D::_ZTv0_n24_N7Point3DD0Ev
112 (int (*)(...))Point3D::_ZTv0_n32_N7Point3D9allAddOneEv
120 (int (*)(...))Point3D::_ZTv0_n40_NK7Point3D1zEv
VTT for Point3D
Point3D::_ZTT7Point3D: 2 entries
0 ((& Point3D::_ZTV7Point3D) + 24)
8 ((& Point3D::_ZTV7Point3D) + 96)
Class Point3D
size=32 align=8
base size=12 base align=8
Point3D (0x0x7ff51797d1a0) 0
vptridx=0 vptr=((& Point3D::_ZTV7Point3D) + 24)
Point2D (0x0x7ff517ae7de0) 16 virtual
vptridx=8 vbaseoffset=-24 vptr=((& Point3D::_ZTV7Point3D) + 96)
Point3D对象的结构还是比较简单的,如下:
(gdb) x/8xw p3d
0x8414e70: 0x08201cd0 0x00000000 0x00000003 0x00000000
0x8414e80: 0x08201d18 0x00000000 0x00000001 0x00000002
很明显0x08201cd0是Point3D新增的虚表指针,结合类信息,我们知道其指向了((& Point3D::_ZTV7Point3D) + 24);而0x08201d18是继承自虚基类的放虚表指针的地方,只不过这里放了Derived类自己的虚表指针,其指向了((& Point3D::_ZTV7Point3D) + 96)。
我们关注的重点是虚基类的虚函数表和其中虚函数的实现:虚函数表中放的是什么的地址?不像没有虚基类的多重继承那样各个对象的偏移是一定的,(在只有指针或引用的情况下)虚继承下虚基类的偏移是运行时才能知道的,其中的虚函数又是如何调整this指针的呢?
(gdb) x/4ag 0x08201d18
0x8201d18 <_ZTV7Point3D+96>: 0x8000ba6 <_ZTv0_n24_N7Point3DD1Ev> 0x8000bdb <_ZTv0_n24_N7Point3DD0Ev>
0x8201d28 <_ZTV7Point3D+112>: 0x8000c24 <_ZTv0_n32_N7Point3D9allAddOneEv> 0x8000c3f <_ZTv0_n40_NK7Point3D1zEv>
正如类信息中展示的那样,虚基类的虚表中放置的正是这几个函数名字,但这几个函数是什么呢?我们使用c++filt看一下:
$ c++filt _ZTv0_n24_N7Point3DD1Ev
virtual thunk to Point3D::~Point3D()
$ c++filt _ZTv0_n24_N7Point3DD0Ev
virtual thunk to Point3D::~Point3D()
$ c++filt _ZTv0_n32_N7Point3D9allAddOneEv
virtual thunk to Point3D::allAddOne()
$ c++filt _ZTv0_n40_NK7Point3D1zEv
virtual thunk to Point3D::z() const
可以看到他们被称为virtual thunk,看来是和thunk相似的技术,用来调整this指针和返回值,我们来看看其内部是怎么运行的:
和我们前面讨论的thunk非常像,都是调整this指针,只是前面的thunk里this指针调整的值是固定的,而这里this指针调整的值是动态的放在vptr[-3]
处,我们再看一下这里放的是什么,我们直接看g++生成的类信息,虚表指针是指向((& Point3D::_ZTV7Point3D) + 96),那vptr[-3]
就应该是((& Point3D::_ZTV7Point3D) + 72)放的东西了,可以看到是18446744073709551600,把这个值当作一个long类型的值的话正好是-16,这不就是从Point2D*
类型转化为Point3D*
类型需要减的值嘛(因为Point2D在Point3D类的实体中偏移为16)。我们再检查一下其他的virtual thunk是不是也是一样?
嗯,没问题,再看看下一个:
不好,出现不一样了,这次偏移是vptr[-4]
这里,也就是((& Point3D::_ZTV7Point3D) + 64)放的东西,可以看到是1844674