直接开始吧。主要是各种dump
比较多,内容没多少的!
#include <iostream>
using namespace std;
class TestClass {
public:
TestClass() :Field1(10), Field2(20){}
void Print() {
cout << "TestClass::Print()" << endl;
}
private:
int Field1;
int Field2;
};
通过dump
,我们能得到以下的资讯!
已启动生成…
1>------ 已启动生成: 项目: cppRTTI, 配置: Debug Win32 ------
1>Main.cpp
1>Test2.cpp
1>class TestClass size(8):
1> +---
1> 0 | Field1
1> 4 | Field2
1> +---
1>正在生成代码...
1>cppRTTI.vcxproj -> G:\other_workspace\cppRTTI\Debug\cppRTTI.exe
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
既然起始的第一个就是成员对象(固定是int
,方便后面的查找,其他类型按大小做偏移就可以了!)
void DumpClassLayout() {
TestClass class1;
cout << "sizeof(class1) == " << sizeof(class1) << endl;
int* p = (int*)&class1;
for (int i = 0; i < sizeof(class1) / sizeof(int); i++, p++)
{
cout << "class1.field" << i+1 << " = " << *p << ", address = " << p << endl;
}
}
int main() {
DumpClassLayout();
int pause;
cin >> pause;
return 0;
}
这里解释一下
int* p = (int*)&class1;
(已经会的可以跳过!) 由于class1
对象的内存布局里,第一个是一个int
类型,所以第一块内存是存放一个int
类型的,但是我们是通过&class1
获取到的地址,所以通过强转后,得到的就是第一个成员的地址int *
!
执行结果:
sizeof(class1) == 8
class1.field1 = 10, address = 003CF670
class1.field2 = 20, address = 003CF674
从现在看到结果看来,函数是不放在对象内存中的!
使得TestClass
继承BaseClass
class BaseClass {
public:
BaseClass() :BaseField1(-10) {}
private:
int BaseField1;
};
class TestClass : BaseClass {
public:
TestClass() :Field1(10), Field2(20){}
void Print() {
cout << "TestClass::Print()" << endl;
}
private:
int Field1;
int Field2;
};
同样适用前面的代码,得到以下结果:
已启动重新生成…
1>------ 已启动全部重新生成: 项目: cppRTTI, 配置: Debug Win32 ------
1>Main.cpp
1>Test2.cpp
1>class TestClass size(12):
1> +---
1> 0 | +--- (base class BaseClass)
1> 0 | | BaseField1
1> | +---
1> 4 | Field1
1> 8 | Field2
1> +---
1>正在生成代码...
1>cppRTTI.vcxproj -> G:\other_workspace\cppRTTI\Debug\cppRTTI.exe
========== 全部重新生成: 成功 1 个,失败 0 个,跳过 0 个 ==========
sizeof(class1) == 12
class1.field1 = -10, address = 010FF774
class1.field2 = 10, address = 010FF778
class1.field3 = 20, address = 010FF77C
从结果来看,子类的内存是在前面的!
改成
class TestClass : virtual BaseClass
使用虚继承看看:
已启动生成…
1>------ 已启动生成: 项目: cppRTTI, 配置: Debug Win32 ------
1>Test2.cpp
1>class TestClass size(16):
1> +---
1> 0 | {vbptr}
1> 4 | Field1
1> 8 | Field2
1> +---
1> +--- (virtual base BaseClass)
1>12 | BaseField1
1> +---
1>TestClass::$vbtable@:
1> 0 | 0
1> 1 | 12 (TestClassd(TestClass+0)BaseClass)
1>vbi: class offset o.vbptr o.vbte fVtorDisp
1> BaseClass 12 0 4 0
1>cppRTTI.vcxproj -> G:\other_workspace\cppRTTI\Debug\cppRTTI.exe
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
sizeof(class1) == 16
class1.field1 = 5942204, address = 00B5FB88
class1.field2 = 10, address = 00B5FB8C
class1.field3 = 20, address = 00B5FB90
class1.field4 = -10, address = 00B5FB94
虚继承后,内存布局一下子就不一样了!(同样是之前的代码)
先是{vbptr}
然后是TestClass
自身的成员,最后才是虚继承的类BaseClass
的成员
原因的话,了解
菱形继承
的估计都能大致猜到原因了!
删除虚继承后尝试添加一个虚函数
class TestClass : BaseClass {
...
virtual void Print2() {
cout << "TestClass::Print2()" << endl;
}
virtual void Print3() {
cout << "TestClass::Print3()" << endl;
}
}
TestClass
的布局就变成这样了:
已启动重新生成…
1>------ 已启动全部重新生成: 项目: cppRTTI, 配置: Debug Win32 ------
1>Main.cpp
1>Test2.cpp
1>class TestClass size(16):
1> +---
1> 0 | {vfptr}
1> 4 | +--- (base class BaseClass)
1> 4 | | BaseField1
1> | +---
1> 8 | Field1
1>12 | Field2
1> +---
1>TestClass::$vftable@:
1> | &TestClass_meta
1> | 0
1> 0 | &TestClass::Print2
1> 1 | &TestClass::Print3
1>TestClass::Print2 this adjustor: 0
1>TestClass::Print3 this adjustor: 0
1>正在生成代码...
1>cppRTTI.vcxproj -> G:\other_workspace\cppRTTI\Debug\cppRTTI.exe
========== 全部重新生成: 成功 1 个,失败 0 个,跳过 0 个 ==========
在对象内存的第一个是{vfptr}
(不是{vbptr}
);
如果在有虚函数和虚继承的情况下,内存布局又是怎么样的呢?
已启动生成…
1>------ 已启动生成: 项目: cppRTTI, 配置: Debug Win32 ------
1>Test2.cpp
1>class TestClass size(20):
1> +---
1> 0 | {vfptr}
1> 4 | {vbptr}
1> 8 | Field1
1>12 | Field2
1> +---
1> +--- (virtual base BaseClass)
1>16 | BaseField1
1> +---
1>TestClass::$vftable@:
1> | &TestClass_meta
1> | 0
1> 0 | &TestClass::Print2
1> 1 | &TestClass::Print3
1>TestClass::$vbtable@:
1> 0 | -4
1> 1 | 12 (TestClassd(TestClass+4)BaseClass)
1>TestClass::Print2 this adjustor: 0
1>TestClass::Print3 this adjustor: 0
1>vbi: class offset o.vbptr o.vbte fVtorDisp
1> BaseClass 16 4 4 0
1>cppRTTI.vcxproj -> G:\other_workspace\cppRTTI\Debug\cppRTTI.exe
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
既然第一个地址是虚函数表{vfptr}
,那么我们尝试一下获取并执行一下看看!
static int g_int = 1111;
typedef void (*Function)(TestClass cl);
void DumpClassLayout() {
TestClass class1;
cout << "static int address = " << &g_int << endl;
cout << "sizeof(class1) = " << sizeof(class1) << " address = "<< &class1 << endl;
int* p = (int*)&class1;
for (int i = 0; i < sizeof(class1) / sizeof(int); i++, p++)
{
cout << "class1.field" << i+1 << " = " << *p << ", address = " << p << endl;
}
int** functionPtr = (int**)*(int**)&class1;
while (*functionPtr) {
cout << "vftable address = " << functionPtr << " address Value = " << *functionPtr << endl;
Function func = (Function)*functionPtr;
func(class1);
functionPtr++;
}
}
添加了数据区地址的获取;(全局静态数据)
static int address = 00A2D000
sizeof(class1) = 16 address = 0133FA54
class1.field1 = 10660932, address = 0133FA54
class1.field2 = -10, address = 0133FA58
class1.field3 = 10, address = 0133FA5C
class1.field4 = 20, address = 0133FA60
vftable address = 00A2AC44 address Value = 00A212F8
TestClass::Print2()
vftable address = 00A2AC48 address Value = 00A21082
TestClass::Print3()
$00A2AC44$=$10660932$
通过对地址的比较,我们知道虚函数表是处于内存的代码段区
的(位于全局静态数据区
前);而虚表对应的函数地址还要再虚表
的地址前。
以上都是在
x86
的情况运行的,x86的一个指针是4位,在x64
的时候是8位,而int
都是4位,所以在x64
的情况下,我们能看到对齐和被拆成两部分的虚表地址!
static int address = 00007FF79E8CF000
sizeof(class1) = 24 address = 000000DEF70FF5C8
class1.field1 = -1634943120, address = 000000DEF70FF5C8
class1.field2 = 32759, address = 000000DEF70FF5CC
class1.field3 = -10, address = 000000DEF70FF5D0
class1.field4 = 10, address = 000000DEF70FF5D4
class1.field5 = 20, address = 000000DEF70FF5D8
class1.field6 = -858993460, address = 000000DEF70FF5DC
vftable address = 00007FF79E8CBF70 address Value = 00007FF79E8C128A
TestClass::Print2()
vftable address = 00007FF79E8CBF78 address Value = 00007FF79E8C143D
TestClass::Print3()
前面的代码已经具有以下特征:
TestClass
- int Field1
- int Field2
- virtual void Print2()
- virtual void Print3()
BaseClass
- BaseField1
现在我们试试在BaseClass
里添加三个虚函数
class BaseClass {
public:
...
virtual void Print2() {
cout << "BaseClass::Print2()" << endl;
}
virtual void Print4() {
cout << "BaseClass::Print4()" << endl;
}
void Print5() {
cout << "BaseClass::Print5()" << endl;
}
}
改造完成后,TestClass
的内存布局就成这样:
已启动生成…
1>------ 已启动生成: 项目: cppRTTI, 配置: Debug Win32 ------
1>Test2.cpp
1>class TestClass size(16):
1> +---
1> 0 | +--- (base class BaseClass)
1> 0 | | {vfptr}
1> 4 | | BaseField1
1> | +---
1> 8 | Field1
1>12 | Field2
1> +---
1>TestClass::$vftable@:
1> | &TestClass_meta
1> | 0
1> 0 | &TestClass::Print2
1> 1 | &BaseClass::Print4
1> 2 | &TestClass::Print3
1>TestClass::Print2 this adjustor: 0
1>TestClass::Print3 this adjustor: 0
1>cppRTTI.vcxproj -> G:\other_workspace\cppRTTI\Debug\cppRTTI.exe
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
{vfptr}
虚表放到了基类去了,但是是从整体上看,对象的第一个指针还是虚表!
TestClass::Print2
在没有overrider
的情况下,还是能实现重载的!
下面在来个virtual
继承的大综合看看
class TestClass : virtual BaseClass
已启动生成…
1>------ 已启动生成: 项目: cppRTTI, 配置: Debug Win32 ------
1>Test2.cpp
1>class TestClass size(28):
1> +---
1> 0 | {vfptr}
1> 4 | {vbptr}
1> 8 | Field1
1>12 | Field2
1> +---
1>16 | (vtordisp for vbase BaseClass)
1> +--- (virtual base BaseClass)
1>20 | {vfptr}
1>24 | BaseField1
1> +---
1>TestClass::$vftable@TestClass@:
1> | &TestClass_meta
1> | 0
1> 0 | &TestClass::Print3
1>TestClass::$vbtable@:
1> 0 | -4
1> 1 | 16 (TestClassd(TestClass+4)BaseClass)
1>TestClass::$vftable@BaseClass@:
1> | -20
1> 0 | &(vtordisp) TestClass::Print2
1> 1 | &BaseClass::Print4
1>TestClass::Print2 this adjustor: 20
1>TestClass::Print3 this adjustor: 0
1>vbi: class offset o.vbptr o.vbte fVtorDisp
1> BaseClass 20 4 4 1
1>cppRTTI.vcxproj -> G:\other_workspace\cppRTTI\Debug\cppRTTI.exe
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
对比一下没有使用虚继承:
- 16 vs 28
首先是对象大小,虚继承的对象内存是28
;对比知道,多了{vfptr}
、{vbptr}
、(vtordisp for vbase BaseClass)
每个都是4字节(x86);
- vtordisp
关于 vtordisp for vbase BaseClass
,MSDN,虚继承中派生类重写了基类的虚函数,并且在构造函数或者析构函数中使用指向基类的指针调用了该函数,编译器会为虚基类添加vtordisp域
。
- vfptr
{vfptr}
这里有两份,一份是在BaseClass
, 一份在TestClass
;但是这种情况不是一定的,如果TestClass
和BaseClass
的Virtual
全部一致(TestClass
也不能多!),那么{vfptr}
就只有一份了!(内存也就减少了!)
以下就是将TestClass::Print3
的Virtual
删除,添加virtual TestClass::Print4()
后
已启动生成…
1>------ 已启动生成: 项目: cppRTTI, 配置: Debug Win32 ------
1>Test2.cpp
1>class TestClass size(24):
1> +---
1> 0 | {vbptr}
1> 4 | Field1
1> 8 | Field2
1> +---
1>12 | (vtordisp for vbase BaseClass)
1> +--- (virtual base BaseClass)
1>16 | {vfptr}
1>20 | BaseField1
1> +---
1>TestClass::$vbtable@:
1> 0 | 0
1> 1 | 16 (TestClassd(TestClass+0)BaseClass)
1>TestClass::$vftable@:
1> | -16
1> 0 | &(vtordisp) TestClass::Print2
1> 1 | &(vtordisp) TestClass::Print4
1>TestClass::Print2 this adjustor: 16
1>TestClass::Print4 this adjustor: 16
1>vbi: class offset o.vbptr o.vbte fVtorDisp
1> BaseClass 16 0 4 1
1>cppRTTI.vcxproj -> G:\other_workspace\cppRTTI\Debug\cppRTTI.exe
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
到这里c++对象的内存基本算是完成了!