c++单继承、多继承、菱形继承内存布局(虚函数表结构)

scorlw 发布于

c++单继承、多继承、菱形继承内存布局(虚函数表结构)

c++

1、单继承的内存布局

单继承:只有一个基类和一个派生类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Base
{
public:
virtual void fun1()
{
cout << "Base::func1()" << endl;
}
virtual void fun2()
{
cout << "Base::func2()" << endl;
}
private:
int b;
};
class Derive :public Base
{
public:
virtual void fun1() //重写基类虚函数,实现多态
{
cout << "Derive::func1()" << endl;
}

virtual void fun3()
{
cout << "Derive::func3()" << endl;
}
void fun4()
{
cout << "Derive::func4()" << endl;
}
private:
int d;
};

在VS中调试的时候可以看到一些虚函数表,但有可能不全,所以我们在这里先写一个函数用于打印虚函数表!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef void (*FUNC)();        //重定义函数指针,指向函数的指针
//打印虚函数表
// 这里的k表示打印的虚函数的个数上限,因为原文中拿到vs中测试的时候发现打印完虚函数后的后面并不总是为0,故增加一个k来使得循环停止
//这里的k需要自己手动设置个数,比如Base中有2个虚函数,那么我们设置2即可。
void PrintVTable(int* vTable,int k)
{
if (vTable == NULL)
{
return;
}
cout << "虚函数表地址:" << vTable << endl;
int i = 0;
for (; vTable[i] != 0&&i<k; ++i)
{
printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
FUNC f = (FUNC)vTable[i];
f(); //访问虚函数
}
cout << endl;
}

测试函数:

1
2
3
4
5
6
7
8
9
void Test1()
{
Base b;
Derive d;
int* tmp = (int*)(*(int*)&b); //取到虚函数的地址
PrintVTable(tmp);
int* tmp1 = (int*)(*(int*)&d);
PrintVTable(tmp1);
}

输出:

1
2
3
4
5
6
7
8
虚函数表地址:0x4c14f0
0个虚函数地址 :0X438690,->Base::func1()
1个虚函数地址 :0X4386c4,->Base::func2()

虚函数表地址:0x4c1500
0个虚函数地址 :0X438728,->Derive::func1()
1个虚函数地址 :0X4386c4,->Base::func2()
2个虚函数地址 :0X43875c,->Derive::func3()

解析:int* tmp = (int)( (int*)&b);
如下图:

这里写图片描述

2、多继承的内存布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Base1   //基类
{
public:
virtual void fun1()
{
cout << "Base1::fun1" << endl;
}
virtual void fun2()
{
cout << "Base1::fun2" << endl;
}
private:
int b1;
};
class Base2 //基类
{
public:
virtual void fun1()
{
cout << "Base2::fun1" << endl;
}
virtual void fun2()
{
cout << "Base2::fun2" << endl;
}
private:
int b2;
};
class Derive : public Base1, public Base2 //派生类
{
public:
virtual void fun1()
{
cout << "Derive::fun1" << endl;
}
virtual void fun3()
{
cout << "Derive::fun3" << endl;
}
private:
int d1;
};

测试函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Test1()
{
Base1 b1;
int* tmp1 = (int*)(*(int*)&b1); //取到虚函数的地址
PrintVTable(tmp1,2);

Base2 b2;
int* tmp2 = (int*)(*(int*)&b2); //取到虚函数的地址
PrintVTable(tmp2,2);

Derive d1;
int* VTable = (int*)(*(int*)&d1);
PrintVTable(VTable, 3);
VTable = (int*)(*((int*)&d1 + sizeof (Base1)/4));
PrintVTable(VTable, 2);
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
虚函数表地址:0x4c1510
0个虚函数地址 :0X4386e0,->Base1::fun1
1个虚函数地址 :0X438714,->Base1::fun2

虚函数表地址:0x4c1520
0个虚函数地址 :0X438778,->Base2::fun1
1个虚函数地址 :0X4387ac,->Base2::fun2

虚函数表地址:0x4c1530
0个虚函数地址 :0X438810,->Derive::fun1
1个虚函数地址 :0X438714,->Base1::fun2
2个虚函数地址 :0X438844,->Derive::fun3

虚函数表地址:0x4c1544
0个虚函数地址 :0X4b5860,->Derive::fun1
1个虚函数地址 :0X4387ac,->Base2::fun2

解析:VTable = (int*)(((int)&d1 + sizeof (Base1)/4));

这里写图片描述

3、菱形继承(非虚继承)的内存布局

菱形继承会出现子对象重叠的问题。

子对象重叠:两个子类base1和base2继承同一父类base,而又有子类继承这两个子类。会产生二义性问题,即对于base的调用需要指定作用域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Base          //Derive的间接基类
{
public:
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func2()
{
cout << "Base::func2()" << endl;
}
private:
int b;
};
class Base1 :public Base //Derive的直接基类
{
public:
virtual void func1() //重写Base的func1()
{
cout << "Base1::func1()" << endl;
}
virtual void func3()
{
cout << "Base1::func3()" << endl;
}
private:
int b1;
};
class Base2 :public Base //Derive的直接基类
{
public:
virtual void func1() //重写Base的func1()
{
cout << "Base2::func2()" << endl;
}
virtual void func4()
{
cout << "Base2::func4()" << endl;
}
private:
int b2;
};
class Derive :public Base1, public Base2
{
public:
virtual void func1() //重写Base1的func1()
{
cout << "Derive::func1()" << endl;
}
virtual void func5()
{
cout << "Derive::func5()" << endl;
}
private:
int d;
};

测试函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void Test1()
{
Base b;
Base1 b1;
Base2 b2;
Derive d;
printf("sizeof(Base) = %d\n",sizeof(b));
printf("sizeof(Base1) = %d\n",sizeof(b1));
printf("sizeof(Base2) = %d\n",sizeof(b2));
printf("sizeof(Derive) = %d\n",sizeof(d));

int* tmp = (int*)(*(int*)&b);
PrintVTable(tmp,2);

int* tmp1 = (int*)(*(int*)&b1); //取到虚函数的地址
PrintVTable(tmp1,3);

int* tmp2 = (int*)(*(int*)&b2); //取到虚函数的地址
PrintVTable(tmp2,3);

int* VTable = (int*)(*(int*)&d);
PrintVTable(VTable, 4);
VTable = (int*)(*((int*)&d + sizeof (Base1)/4));
PrintVTable(VTable, 3);
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
sizeof(Base) = 8
sizeof(Base1) = 12
sizeof(Base2) = 12
sizeof(Derive) = 28
Base虚函数表地址:0x4c25d0
0个虚函数地址 :0X4387b0,->Base::func1()
1个虚函数地址 :0X4387e4,->Base::func2()

Base1虚函数表地址:0x4c25e0
0个虚函数地址 :0X438848,->Base1::func1()
1个虚函数地址 :0X4387e4,->Base::func2()
2个虚函数地址 :0X43887c,->Base1::func3()

Base2虚函数表地址:0x4c25f4
0个虚函数地址 :0X4388f0,->Base2::func2()
1个虚函数地址 :0X4387e4,->Base::func2()
2个虚函数地址 :0X438924,->Base2::func4()

Derive虚函数表地址:0x4c2608
0个虚函数地址 :0X438998,->Derive::func1()
1个虚函数地址 :0X4387e4,->Base::func2()//func2
2个虚函数地址 :0X43887c,->Base1::func3()
3个虚函数地址 :0X4389cc,->Derive::func5()

虚函数表地址:0x4c2620
0个虚函数地址 :0X4b59f0,->Derive::func1()
1个虚函数地址 :0X4387e4,->Base::func2()//func2
2个虚函数地址 :0X438924,->Base2::func4()

注意:两个func2()是不同的函数,即使都是继承于Base。

内存布局如图:

这里写图片描述

4、菱形继承(虚继承)的内存布局

关于虚继承的两种内存布局方案

这里写图片描述

我们主要解析第一种方案:

vs2003下虚继承的VBPTR及VBTBL:
在类中增加一个指针(VBPTR)指向一个VBTBL,这个VBTBL的第一项记载的是从VBPTR 与本类的偏移地址,如果本类有虚函数,那么第一项是FF FF FF FC(也就是-4),如果没有则是零,第二项起是VBPTR与本类的虚基类的偏移值。

代码:菱形继承(虚继承)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Base
{
public:
virtual void fun1()
{
cout << "Base::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base::fun2()" << endl;
}
private:
int b;
};
class Base1 :virtual public Base 虚继承
{
public:
virtual void fun1() //重写Base的func1()
{
cout << "Base1::fun1()" << endl;
}
virtual void fun3()
{
cout << "Base1::fun3()" << endl;
}
private:
int b1;
};
class Base2 :virtual public Base //虚继承
{
public:
virtual void fun1() //重写Base的func1()
{
cout << "Base2::fun1()" << endl;
}
virtual void fun4()
{
cout << "Base2::fun4()" << endl;
}
private:
int b2;
};
class Derive :public Base1, public Base2
{
public:
virtual void fun1() //重写Base1的func1()
{
cout << "Derive::fun1()" << endl;
}
virtual void fun5()
{
cout << "Derive::fun5()" << endl;
}
private:
int d;
};

1. 详细地分析一下vs2003下虚继承的VBPTR及VBTBL:

以Base1 b1;为例子,详细分析内存布局如下(Base2和Base1的内存布局相似):

这里写图片描述

2、我们再来探索一下 Derive d 的内存布局

这里写图片描述

我们怎么验证菱形继承(虚继承)的内存布局就是这样的呢?我们可以通过打印虚表!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef void(*FUNC)();
void PrintVPTR(int* VPTR) //打印虚表(虚函数)
{
cout << "虚函数表地址:" << VPTR << endl;
for (int i = 0; VPTR[i] != 0; ++i)
{
printf("第%d个虚函数地址:0X%x->", i, VPTR[i]);
FUNC f = (FUNC)VPTR[i];
f();
}
cout << endl;
}
void PrintVBPTR(int* VBPTR) //打印偏移地址与值
{
cout << "虚函数表地址:" << VBPTR << endl;
int i = 0;
printf("与本类的偏移地址:0X%x\n", VBPTR[i]);
for (i = 1; VBPTR[i] != 0; i++)
{
cout << VBPTR[i] << " " << endl;
}
cout << endl;
}

(本地测试失败)

原文地址:https://blog.csdn.net/u010235142/article/details/78307022