多重继承的内存结构

单重虚继承无虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BaseClass
{
public:
int v1 = 0xaaaaaaaa;
int v2 = 0xbbbbbbbb;
};

class MyClass : virtual public BaseClass
{
public:
int v3 = 0xcccccccc;
int v4 = 0xdddddddd;
int v5 = 0xeeeeeeee;
int v6 = 0xffffffff;
};

void main()
{
MyClass myClass;
}

在内存窗口中查看 myClass

1
2
3
4
5
6
7
0x0019FEF4  58 58 41 00  XXA. -----子类
0x0019FEF8 cc cc cc cc ????
0x0019FEFC dd dd dd dd ????
0x0019FF00 ee ee ee ee ????
0x0019FF04 ff ff ff ff .... -----子类
0x0019FF08 aa aa aa aa ???? -----基类
0x0019FF0C bb bb bb bb ???? -----基类

可以看到此时的内存结构和非虚继承有差别,基类的内存位于子类的内存之下

此外在内存结构的顶端,出现了一个额外的指针,此指针指向的位置为基类的偏移表

打开0x00415858

1
2
0x00415858  00 00 00 00  ....
0x0041585C 14 00 00 00 ....

首地址为保留区域,第二项所保存的内存值为 0x14 ,即为在子类内存中的基类区域到子类首地址的偏移量,也就是在第一个内存窗口中 0x0019FF04 - 0x0019FEF4 的值

单重虚继承有虚函数

父类有虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class BaseClass
{
public:
int v1 = 0xaaaaaaaa;
int v2 = 0xbbbbbbbb;
virtual void func(){};
};

class MyClass : virtual public BaseClass
{
public:
int v3 = 0xcccccccc;
int v4 = 0xdddddddd;
int v5 = 0xeeeeeeee;
int v6 = 0xffffffff;
};

void main()
{
MyClass myClass;
}

myClass的内存结构为

1
2
3
4
5
6
7
8
0x0019FEF0  70 58 41 00  pXA.-----子类(基类偏移区指针)
0x0019FEF4 cc cc cc cc ????
0x0019FEF8 dd dd dd dd ????
0x0019FEFC ee ee ee ee ????
0x0019FF00 ff ff ff ff ....-----子类
0x0019FF04 68 58 41 00 hXA.-----基类(基类虚函数指针)
0x0019FF08 aa aa aa aa ????
0x0019FF0C bb bb bb bb ????-----基类

子类重写父类虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class BaseClass
{
public:
int v1 = 0xaaaaaaaa;
int v2 = 0xbbbbbbbb;
virtual void func(){};
};

class MyClass : virtual public BaseClass
{
public:
int v3 = 0xcccccccc;
int v4 = 0xdddddddd;
int v5 = 0xeeeeeeee;
int v6 = 0xffffffff;
virtual void func(){};
};

void main()
{
MyClass myClass;
}

打开 myClass 的内存窗口

1
2
3
4
5
6
7
8
0x0019FEF0  70 68 41 00  phA.
0x0019FEF4 cc cc cc cc ????
0x0019FEF8 dd dd dd dd ????
0x0019FEFC ee ee ee ee ????
0x0019FF00 ff ff ff ff ....
0x0019FF04 68 68 41 00 hhA.
0x0019FF08 aa aa aa aa ????
0x0019FF0C bb bb bb bb ????

内存结构同上

子类添加虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BaseClass
{
public:
int v1 = 0xaaaaaaaa;
int v2 = 0xbbbbbbbb;
virtual void func(){};
};

class MyClass : virtual public BaseClass
{
public:
int v3 = 0xcccccccc;
int v4 = 0xdddddddd;
int v5 = 0xeeeeeeee;
int v6 = 0xffffffff;
virtual void func(){};
virtual void func1(){}; //新增
};

void main()
{
MyClass myClass;
}

打开 myClass 的内存

1
2
3
4
5
6
7
8
9
0x0019FEEC  68 68 41 00  hhA. ---子类虚函数指针
0x0019FEF0 78 68 41 00 xhA. ---子类偏移指针
0x0019FEF4 cc cc cc cc ???? ---子类数据
0x0019FEF8 dd dd dd dd ???? ---子类数据
0x0019FEFC ee ee ee ee ???? ---子类数据
0x0019FF00 ff ff ff ff .... ---子类数据
0x0019FF04 74 68 41 00 thA. ---父类偏移指针(其中包含虚函数表)
0x0019FF08 aa aa aa aa ???? ---父类数据
0x0019FF0C bb bb bb bb ???? ---父类数据

多重继承有虚函数

父类有虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class BaseClass1
{
public:
int v1 = 0xaaaaaaaa;
virtual void func1(){};
};

class BaseClass2
{
public:
int v2 = 0xbbbbbbbb;
virtual void func2(){};
};

class MyClass : public BaseClass1, public BaseClass2
{
public:
int v3 = 0xcccccccc;
};

void main()
{
MyClass myClass;
}

打开 myClass的内存窗口

1
2
3
4
5
0x0019FEFC  74 68 41 00  thA.---父类BaseClass1 的虚函数表指针
0x0019FF00 aa aa aa aa ????---父类BaseClass1 内存
0x0019FF04 7c 68 41 00 |hA. ---父类BaseClass2 的虚函数表指针
0x0019FF08 bb bb bb bb ?????---父类BaseClass2 内存
0x0019FF0C cc cc cc cc ???? ---子类 内存

其中,父类 BaseClass1 的虚函数表中包含了 BaseClass2 的虚函数

打开 BaseClass1 的虚函数指针 0x00416874

1
2
3
4
0x00416874  2b 12 41 00  +.A. ---BaseClass1::fun1()
0x00416878 48 7a 41 00 HzA. ---未知值
0x0041687C 35 12 41 00 5.A. ---BaseClass2::fun2()
0x00416880 00 00 00 00 ....

只有一个父类有虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BaseClass1
{
public:
int v1 = 0xaaaaaaaa;
};

class BaseClass2
{
public:
int v2 = 0xbbbbbbbb;
virtual void func2(){};
};

class MyClass : public BaseClass1, public BaseClass2
{
public:
int v3 = 0xcccccccc;
};

void main()
{
MyClass myClass;
}

打开 myClass的内存窗口

1
2
3
4
0x0019FF00  68 68 41 00  hhA. ---父类BaseClass2 的虚函数表指针
0x0019FF04 bb bb bb bb ???? ---父类BaseClass2 内存
0x0019FF08 aa aa aa aa ???? ---父类BaseClass1 内存
0x0019FF0C cc cc cc cc ???? ---子类 内存

发现此时的内存顺序并不是按照继承顺序来排列,而是按照父类中有无虚函数的状态进行排序

即有虚函数的父类优先

子类重写父类的虚函数

子类重写的虚函数会覆盖父类虚表中的对应虚函数的位置,内存结构无差异

子类新加虚函数

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
class BaseClass1
{
public:
int v1 = 0xaaaaaaaa;
virtual void func1(){};
};

class BaseClass2
{
public:
int v2 = 0xbbbbbbbb;
virtual void func2(){};
};

class MyClass : public BaseClass1, public BaseClass2
{
public:
int v3 = 0xcccccccc;
virtual void func3(){};
};

void main()
{
MyClass myClass;
}

打开 myClass的内存窗口

1
2
3
4
5
0x0019FEFC  70 68 41 00  phA.
0x0019FF00 aa aa aa aa ????
0x0019FF04 7c 68 41 00 |hA.
0x0019FF08 bb bb bb bb ????
0x0019FF0C cc cc cc cc ????

此时并未给子类添加虚函数表指针

打开第一父类 BaseClass1 的虚函数表指针

其中除了第一父类 BaseClass1 的虚函数,依次包含了子类新添加的虚函数,以及 BaseClass2 的虚函数

1
2
3
4
5
0x00416870  3f 12 41 00  ?.A. ---BaseClass1::func1()
0x00416874 44 12 41 00 D.A. ---MyClass::func3()
0x00416878 48 7a 41 00 HzA.
0x0041687C 35 12 41 00 5.A. ---BaseClass2::func3()
0x00416880 00 00 00 00 ....

###

多重虚继承(菱形继承)无虚函数

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
class BaseClass
{
public:
int v1 = 0xaaaaaaaa;

};

class FatherClassA : virtual public BaseClass
{
public:
int v2 = 0xbbbbbbbb;
};

class FatherClassB : virtual public BaseClass
{
public:
int v3 = 0xcccccccc;
};

class SonClass : public FatherClassA, public FatherClassB
{
public:
int v4 = 0xdddddddd;
};

void main()
{
SonClass sonClass;
}

打开 sonClass的内存窗口

1
2
3
4
5
6
0x0019FEF8  70 58 41 00  pXA.---父类FatherClassA 的偏移量指针
0x0019FEFC bb bb bb bb ????---父类FatherClassA 的内存
0x0019FF00 7c 58 41 00 |XA.---父类FatherClassB 的偏移量指针
0x0019FF04 cc cc cc cc ????---父类FatherClassB 的内存
0x0019FF08 dd dd dd dd ????---子类 的内存
0x0019FF0C aa aa aa aa ????---基类BaseClass 的内存

打开 FatherClassA 的偏移量指针 0x00415870

1
2
0x00415870  00 00 00 00  ....
0x00415874 14 00 00 00 ....

其中偏移值为 0x14

偏移值的含义为基类的内存到第一父类 FatherClassA 的偏移量

也就是 0x0019FF0C - 0x0019FEF8 = 0x00000014

打开 FatherClassB 的偏移量指针 0x0041587c

1
2
0x0041587C  00 00 00 00  ....
0x00415880 0c 00 00 00 ....

其中偏移值为 0x0c

偏移值的含义为基类的内存到第二父类 FatherClassB 的偏移量

也就是 0x0019FF0C - 0x0019FF00= 0x0000000C

多重虚继承(菱形继承)有虚函数

只有虚基类有虚函数

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
class BaseClass
{
public:
int v1 = 0xaaaaaaaa;
virtual void func(){};
};

class FatherClassA : virtual public BaseClass
{
public:
int v2 = 0xbbbbbbbb;
};

class FatherClassB : virtual public BaseClass
{
public:
int v3 = 0xcccccccc;
};

class SonClass : public FatherClassA, public FatherClassB
{
public:
int v4 = 0xdddddddd;
};
void main()
{
SonClass sonClass;
}

打开 sonClass的内存窗口

1
2
3
4
5
6
7
0x0019FEF4  a0 68 41 00  ?hA.---父类FatherClassA 的偏移量指针
0x0019FEF8 bb bb bb bb ????---父类FatherClassA 的内存
0x0019FEFC ac 68 41 00 ?hA.---父类FatherClassB 的偏移量指针
0x0019FF00 cc cc cc cc ????---父类FatherClassB 的内存
0x0019FF04 dd dd dd dd ????---子类内存
0x0019FF08 98 68 41 00 ?hA.---基类虚函数表指针
0x0019FF0C aa aa aa aa ????---基类内存

中间类重写虚基类的虚函数

此时允许两个中间类同时重写基类中的虚函数,因为中间类都是虚继承基类,相当于它们共享了唯一一份基类

所以如果同时重写无异于重复定义,在子类中会报错

仅允许其中一个中间类重写虚基类的虚函数

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
class BaseClass
{
public:
int v1 = 0xaaaaaaaa;
virtual void func(){};

};

class FatherClassA : virtual public BaseClass
{
public:
int v2 = 0xbbbbbbbb;
virtual void func(){};

};

class FatherClassB : virtual public BaseClass
{
public:
int v3 = 0xcccccccc;


};

class SonClass : public FatherClassA, public FatherClassB
{
public:
int v4 = 0xdddddddd;
};

内存结构同 只有虚基类有虚函数 的内存结构

中间类新加虚函数

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
class BaseClass
{
public:
int v1 = 0xaaaaaaaa;
virtual void func(){};

};

class FatherClassA : virtual public BaseClass
{
public:
int v2 = 0xbbbbbbbb;
virtual void func1(){};
};

class FatherClassB : virtual public BaseClass
{
public:
int v3 = 0xcccccccc;
virtual void func1(){};
};

class SonClass : public FatherClassA, public FatherClassB
{
public:
int v4 = 0xdddddddd;
};
void main()
{
SonClass sonClass;
}

打开 sonClass的内存窗口

1
2
3
4
5
6
7
8
9
0x0019FEEC  b0 68 41 00  ?hA. ---父类FatherClassA的虚函数表指针
0x0019FEF0 20 69 41 00 iA. ---父类FatherClassA的偏移量指针
0x0019FEF4 bb bb bb bb ???? ---父类FatherClassA内存
0x0019FEF8 c4 68 41 00 ?hA. ---父类FatherClassB的虚函数表指针
0x0019FEFC 88 69 41 00 ?iA. ---父类FatherClassB的偏移量指针
0x0019FF00 cc cc cc cc ???? ---父类FatherClassB内存
0x0019FF04 dd dd dd dd ???? ---子类内存
0x0019FF08 1c 69 41 00 .iA. ---基类SonClass的虚函数表指针
0x0019FF0C aa aa aa aa ???? ---基类内存

对比只有 只有虚基类有虚函数 的内存结构发现,仅仅只是在前者的基础上添加了每个类对应的虚函数表指针

但偏移指针所指向的偏移值有所变化

打开 FatherClassA 中的偏移表指针

1
2
0x00416920  fc ff ff ff  ?...
0x00416924 18 00 00 00 ....

第二个值为 0x18 ,根据之前的推论经验,此为基类到首地址的偏移量

但现在的内存首地址为 0x0019FEEC ,而 0x0019FF08 - 0x180x0019FEF0

所以在此猜测偏移量 0x18 是基类到父类的偏移表指针的偏移量,不包括父类虚函数表指针

于是打开 FatherClassB 中的偏移指针 0x00416988 进行验证

1
2
0x00416988  fc ff ff ff  ?...
0x0041698C 0c 00 00 00 ....

第二个值为 0x0c 正好为 0x0019FF08 - 0x0019FEFC 的值,猜想得到验证

子类重写虚基类的虚函数

内存结构无变化

子类重写中间类的虚函数

内存结构无变化,但重写的虚函数加入到了基类的虚函数表中

子类新加虚函数

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 BaseClass
{
public:
int v1 = 0xaaaaaaaa;
virtual void func(){};

};

class FatherClassA : virtual public BaseClass
{
public:
int v2 = 0xbbbbbbbb;
virtual void func1(){};
};

class FatherClassB : virtual public BaseClass
{
public:
int v3 = 0xcccccccc;
virtual void func2(){};
};

class SonClass : public FatherClassA, public FatherClassB
{
public:
int v4 = 0xdddddddd;
virtual void func3(){};
};

void main()
{
SonClass sonClass;
}

打开 sonClass的内存窗口

1
2
3
4
5
6
7
8
9
0x0019FEEC  ac 68 41 00  ?hA. ---父类FatherClassA的虚函数表指针
0x0019FEF0 20 69 41 00 iA.
0x0019FEF4 bb bb bb bb ????
0x0019FEF8 c4 68 41 00 ?hA. ---父类FatherClassB的虚函数表指针
0x0019FEFC 88 69 41 00 ?iA.
0x0019FF00 cc cc cc cc ????
0x0019FF04 dd dd dd dd ????
0x0019FF08 1c 69 41 00 .iA. ---基类SonClass的虚函数表指针
0x0019FF0C aa aa aa aa ????

根据之前的经验,子类新添加的虚函数应该加入到第一父类中的虚函数表中

打开 FatherClassA 的虚函数表指针 0x004168ac

1
2
3
0x004168AC  21 12 41 00  !.A.
0x004168B0 3a 12 41 00 :.A.
0x004168B4 00 00 01 00 ....

打开汇编窗口

0x00411221 的确为 SonClass::func3()