京东联盟新手没有网站怎么做推广,想建设一个网站自己接一些小活,中山顺德网站建设,招聘网站分析报告怎么做前言
上一章 简述了虚函数的调用链路#xff0c;本章主要探索 C 各种继承关系的类对象的多态特性。
深入探索 C 多态 ① - 虚函数调用链路深入探索 C 多态 ② - 继承关系深入探索 C 多态 ③ - 虚析构 1. 概述
封装#xff0c;继承#xff0c;多态是 C 的三大特性#xf…前言
上一章 简述了虚函数的调用链路本章主要探索 C 各种继承关系的类对象的多态特性。
深入探索 C 多态 ① - 虚函数调用链路深入探索 C 多态 ② - 继承关系深入探索 C 多态 ③ - 虚析构 1. 概述
封装继承多态是 C 的三大特性其中多态与继承有密切关系。C 语言支持三种继承关系单一继承多重继承虚拟继承 图片来源《多型与虚拟》 2. 继承关系
2.1. 单一继承
C 的单一继承是指一个类只能从一个父类继承属性和方法。 文字来源ChatGPT 动态多态的单一继承对象类层次结构相对简单
对象内存只有一个虚指针并且在其首位。虚表上的虚函数通过层层覆盖最终得出对象对应的虚函数表详看下图。
测试代码。
/* g -O0 -stdc11 -fdump-class-hierarchy test.cpp -o test */
#include iostreamclass Base {public:virtual void vBaseFunc() {}virtual void vBaseFunc2() {}virtual void vBaseFunc3() {}long long m_base_data;long long m_base_data2;
};class Base2 : public Base {public:virtual void vBaseFunc() {}virtual void vBase2Func() { std::cout Base2::vBase2Func std::endl; }virtual void vBase2Func2() {}long long m_base2_data;long long m_base2_data2;
};class Derived : public Base2 {public:virtual void vBaseFunc2() {}virtual void vBase2Func() { std::cout Derived::vBase2Func std::endl; }virtual void vDerivedFunc() {}virtual void vDerivedFunc2() {}long long m_derived_data;long long m_derived_data2;
};类布局层次。
Vtable for Base
# _ZTV4Base: vtable for Base
Base::_ZTV4Base: 5u entries
0 (int (*)(...))0
# _ZTI4Base: typeinfo for Base
8 (int (*)(...))( _ZTI4Base)
16 (int (*)(...))Base::vBaseFunc
24 (int (*)(...))Base::vBaseFunc2
32 (int (*)(...))Base::vBaseFunc3Vtable for Base2
Base2::_ZTV5Base2: 7u entries
0 (int (*)(...))0
8 (int (*)(...))( _ZTI5Base2)
16 (int (*)(...))Base2::vBaseFunc
24 (int (*)(...))Base::vBaseFunc2
32 (int (*)(...))Base::vBaseFunc3
40 (int (*)(...))Base2::vBase2Func
48 (int (*)(...))Base2::vBase2Func2# Derived 虚表。
Vtable for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTV7Derived: 9u entries
0 (int (*)(...))0
# _ZTI7Derived: typeinfo for Derived
8 (int (*)(...))( _ZTI7Derived)
16 (int (*)(...))Base2::vBaseFunc
24 (int (*)(...))Derived::vBaseFunc2
32 (int (*)(...))Base::vBaseFunc3
40 (int (*)(...))Derived::vBase2Func
48 (int (*)(...))Base2::vBase2Func2
56 (int (*)(...))Derived::vDerivedFunc
64 (int (*)(...))Derived::vDerivedFunc2# 类的继承关系
Class Derivedsize56 align8base size56 base align8
Derived (0x0x7fb058fa8478) 0# 虚指针指向虚表的位置。vptr(( Derived::_ZTV7Derived) 16u)Base2 (0x0x7fb058fa8a28) 0primary-for Derived (0x0x7fb058fa8478)Base (0x0x7fb058ee87e0) 0primary-for Base2 (0x0x7fb058fa8a28)虚表整合。 对象整体布局。 虚函数调用。 对象首位保存的是虚指针 vptr虚指针指向虚表。虚指针指向的虚表地址向高地址偏移 0x18 个字节这样可以获取 Derived::vBase2Func 虚函数地址然后进行调用。
int main() {auto d new Derived;std::cout d std::endl;auto b static_castBase2 *(d);std::cout b std::endl;b-vBase2Func();return 0;
}// 输出
// 0x13a0010
// 0x13a0010
// Derived::vBase2Func2.2. 多重继承
C 支持多重继承这意味着一个类可以从多个父类继承属性和方法在 C 中可以使用逗号分隔的方式来指定多个父类。 文字来源ChatGPT。 测试代码。
/* g -O0 -stdc11 -fdump-class-hierarchy test.cpp -o test */
#include iostreamclass Base {public:virtual void vBaseFunc() {}virtual void vBaseFunc2() {}long long m_base_data;long long m_base_data2;
};class Base2 {public:virtual void vBase2Func() {}virtual void vBase2Func2() { std::cout Base2::vBase2Func2 std::endl; }long long m_base2_data;long long m_base2_data2;
};class Base3 {public:virtual void vBase3Func() {}virtual void vBase3Func2() {}long long m_base3_data;long long m_base3_data2;
};class Derived : public Base, public Base2, public Base3 {public:virtual void vBaseFunc() {}virtual void vBase2Func2() { std::cout Derived::vBase2Func2 std::endl; }virtual void vBase3Func2() {}virtual void vDerivedFunc() {}virtual void vDerivedFunc2() {}long long m_derived_data;long long m_derived_data2;
};类布局层次。
Vtable for Base
# _ZTV4Base: vtable for Base
Base::_ZTV4Base: 4u entries
0 (int (*)(...))0
# _ZTI4Base: typeinfo for Base
8 (int (*)(...))( _ZTI4Base)
16 (int (*)(...))Base::vBaseFunc
24 (int (*)(...))Base::vBaseFunc2Vtable for Base2
# _ZTV5Base2: vtable for Base2
Base2::_ZTV5Base2: 4u entries
0 (int (*)(...))0
# _ZTI5Base2: typeinfo for Base2
8 (int (*)(...))( _ZTI5Base2)
16 (int (*)(...))Base2::vBase2Func
24 (int (*)(...))Base2::vBase2Func2Vtable for Base3
# _ZTV5Base3: vtable for Base3
Base3::_ZTV5Base3: 4u entries
0 (int (*)(...))0
# _ZTI5Base3: typeinfo for Base3
8 (int (*)(...))( _ZTI5Base3)
16 (int (*)(...))Base3::vBase3Func
24 (int (*)(...))Base3::vBase3Func2Vtable for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTV7Derived: 16u entries
0 (int (*)(...))0
# _ZTI7Derived: typeinfo for Derived
8 (int (*)(...))( _ZTI7Derived)
16 (int (*)(...))Derived::vBaseFunc
24 (int (*)(...))Base::vBaseFunc2
32 (int (*)(...))Derived::vBase2Func2
40 (int (*)(...))Derived::vBase3Func2
48 (int (*)(...))Derived::vDerivedFunc
56 (int (*)(...))Derived::vDerivedFunc2
64 (int (*)(...))-24
72 (int (*)(...))( _ZTI7Derived)
80 (int (*)(...))Base2::vBase2Func
# _ZThn24_N7Derived11vBase2Func2Ev: non-virtual thunk to Derived::vBase2Func2()
88 (int (*)(...))Derived::_ZThn24_N7Derived11vBase2Func2Ev
96 (int (*)(...))-48
104 (int (*)(...))( _ZTI7Derived)
112 (int (*)(...))Base3::vBase3Func
# _ZThn48_N7Derived11vBase3Func2Ev: non-virtual thunk to Derived::vBase3Func2()
120 (int (*)(...))Derived::_ZThn48_N7Derived11vBase3Func2EvClass Derivedsize88 align8base size88 base align8
Derived (0x0x7f4196042348) 0vptr(( Derived::_ZTV7Derived) 16u)Base (0x0x7f4195f3e840) 0primary-for Derived (0x0x7f4196042348)Base2 (0x0x7f4195f3e8a0) 24vptr(( Derived::_ZTV7Derived) 80u)Base3 (0x0x7f4195f3e900) 48vptr(( Derived::_ZTV7Derived) 112u)虚表整合。 首先派生类的虚表与第一个基类的虚表结合成一个虚表单元并覆盖基类的虚函数。其它的基类作为一个独立虚表单元。当派生类虚函数有重写基类的虚函数时基类对应虚函数通过 thunk 技术跳转到第一个虚表单元的对应虚函数。 对象整体布局。由下图可见 多重继承有多个虚指针并指向对应的虚表单元。如果派生类有 N 个多重继承单一基类那么它的对象有 N 个虚指针和虚表单元。 虚函数调用。有了上面内存布局的理解我们应该不难理解下面这个基类指针是怎么调用派生类虚函数的
int main() {auto d new Derived;std::cout d std::endl;auto b static_castBase2 *(d);std::cout b std::endl;b-vBase2Func2();return 0;
}// 输出
// 0x13db010
// 0x13db028
// Derived::vBase2Func2Base2 指针指向存储 vptr2 的地址从对象内存顶部向高地址偏移 0x18 个字节获得 vptr2 虚指针。vptr2 指针指向的虚表地址向高地址偏移 0x8 个字节获得 non-virtual thunk to Derived::vBase2Func2() 地址。通过 non-virtual thunk to Derived::vBase2Func2() 地址跳转到 Derived::vBase2Func2 虚函数获取虚表上对应的虚函数地址进行调用。 通过汇编理解函数 thunk to 跳转的工作原理。
# thunk to 跳转原理汇编。
0000000000400aba non-virtual thunk to Derived::vBase2Func2():# rdi 寄存器保存的是 b 指针指向的地址该地址向低地址偏移 0x18 个字节# 也就是 rdi 寄存器保存的是 Derived 内存首位地址# 换句话说将 Derived 的 this 指针作为参数传入 Derived::vBase2Func2 函数。400aba: 48 83 ef 18 sub $0x18,%rdi# 调用 Derived::vBase2Func2() 函数。400abe: eb d0 jmp 400a90 Derived::vBase2Func2()思考上面多重继承的多态实例对象下面这样释放是否正确详情请参考虚析构。
int main() {Base2* b new Derived;delete b;return 0;
}2.3. 虚拟继承
多重继承可以让一个类具有多个不同父类的特性但也可能引发一些问题比如菱形继承问题。为了解决这个问题C 提供了虚继承和虚基类的概念。虚继承可以解决菱形继承问题确保只有一个实例的共享基类。
在 C 中虚拟继承virtual inheritance是一种特殊的继承方式。它用于解决多重继承中的菱形继承问题。当一个类通过虚拟继承从多个基类继承时只会保留一个基类的实例而不会重复继承。这样可以避免菱形继承带来的二义性和冗余。在虚拟继承中派生类需要使用关键字 “virtual” 来声明基类。 文字来源ChatGPT 因为继承关系中有共享基类为了避免共享基类产生多个对象副本浪费内存虚拟继承的内存布局也会与单一继承和多重继承不一样
公共基类的成员数据存放于对象内存底部。虚拟继承引入 VTTVirtual Table Table构造虚表。虚表前缀引入 vbase_offset 偏移量当前虚表与公共基类虚表的内存位置偏移量。 虚拟继承的类层次关系结构有点复杂有兴趣的朋友可以参考What is the VTT for a class。 2.3.1. 对象整体布局
测试代码。
/* g -O0 -stdc11 -fdump-class-hierarchy test.cpp -o test */
#include iostreamclass Base {public:virtual void vBaseFunc() {}virtual void vBaseFunc2() {}long long m_base_data 0x11;long long m_base_data2 0x12;
};class Base2 : virtual public Base {public:virtual void vBaseFunc() {}virtual void vBase2Func() {}virtual void vBase2Func2() {}long long m_base2_data 0x21;long long m_base2_data2 0x22;
};class Base3 : virtual public Base {public:virtual void vBaseFunc2() {}virtual void vBase3Func() {}virtual void vBase3Func2() { std::cout Base3::vBase3Func2 std::endl; }long long m_base3_data 0x31;long long m_base3_data2 0x32;
};class Derived : public Base2, public Base3 {public:virtual void vBase2Func() {}virtual void vBase3Func2() { std::cout Derived::vBase3Func2 std::endl; }virtual void vDerivedFunc() {}virtual void vDerivedFunc2() {}long long m_derived_data 0x41;long long m_derived_data2 0x42;
};类对象内存布局。 Vtable for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTV7Derived: 21u entries
0 64u
8 (int (*)(...))0
# _ZTI7Derived: typeinfo for Derived
16 (int (*)(...))( _ZTI7Derived)
24 (int (*)(...))Base2::vBaseFunc
32 (int (*)(...))Derived::vBase2Func
40 (int (*)(...))Base2::vBase2Func2
48 (int (*)(...))Derived::vBase3Func2
56 (int (*)(...))Derived::vDerivedFunc
64 (int (*)(...))Derived::vDerivedFunc2
72 40u
80 (int (*)(...))-24
88 (int (*)(...))( _ZTI7Derived)
96 (int (*)(...))Base3::vBaseFunc2
104 (int (*)(...))Base3::vBase3Func
# _ZThn24_N7Derived11vBase3Func2Ev: non-virtual thunk to Derived::vBase3Func2()
112 (int (*)(...))Derived::_ZThn24_N7Derived11vBase3Func2Ev
120 18446744073709551576u # -40
128 18446744073709551552u # -64
136 (int (*)(...))-64
144 (int (*)(...))( _ZTI7Derived)
# _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc()
152 (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv
# _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2()
160 (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2EvConstruction vtable for Base2 (0x0x7fd19d6aea90 instance) in Derived
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
Derived::_ZTC7Derived0_5Base2: 12u entries
0 64u
8 (int (*)(...))0
# _ZTI5Base2: typeinfo for Base2
16 (int (*)(...))( _ZTI5Base2)
24 (int (*)(...))Base2::vBaseFunc
32 (int (*)(...))Base2::vBase2Func
40 (int (*)(...))Base2::vBase2Func2
48 0u
56 18446744073709551552u # -64
64 (int (*)(...))-64
# _ZTI5Base2: typeinfo for Base2
72 (int (*)(...))( _ZTI5Base2)
# _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc()
80 (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv
88 (int (*)(...))Base::vBaseFunc2Construction vtable for Base3 (0x0x7fd19d6aeaf8 instance) in Derived
Derived::_ZTC7Derived24_5Base3: 12u entries
0 40u
8 (int (*)(...))0
# _ZTI5Base3: typeinfo for Base3
16 (int (*)(...))( _ZTI5Base3)
24 (int (*)(...))Base3::vBaseFunc2
32 (int (*)(...))Base3::vBase3Func
40 (int (*)(...))Base3::vBase3Func2
48 18446744073709551576u # -40
56 0u
64 (int (*)(...))-40
# _ZTI5Base3: typeinfo for Base3
72 (int (*)(...))( _ZTI5Base3)
80 (int (*)(...))Base::vBaseFunc
# _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2()
88 (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2EvVTT for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTT7Derived: 7u entries
0 (( Derived::_ZTV7Derived) 24u)
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
8 (( Derived::_ZTC7Derived0_5Base2) 24u)
16 (( Derived::_ZTC7Derived0_5Base2) 80u)
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
24 (( Derived::_ZTC7Derived24_5Base3) 24u)
32 (( Derived::_ZTC7Derived24_5Base3) 80u)
40 (( Derived::_ZTV7Derived) 152u)
48 (( Derived::_ZTV7Derived) 96u)Class Derivedsize88 align8base size64 base align8
Derived (0x0x7fd19d7401c0) 0vptridx0u vptr(( Derived::_ZTV7Derived) 24u)Base2 (0x0x7fd19d6aea90) 0primary-for Derived (0x0x7fd19d7401c0)subvttidx8uBase (0x0x7fd19d5ee840) 64 virtualvptridx40u vbaseoffset-24 vptr(( Derived::_ZTV7Derived) 152u)Base3 (0x0x7fd19d6aeaf8) 24subvttidx24u vptridx48u vptr(( Derived::_ZTV7Derived) 96u)Base (0x0x7fd19d5ee840) alternative-path2.3.2. 构造顺序
我们可以通过类的构造顺序去理解对象内存布局如何一步一步构造出来的。在构造派生类 Derived 时先构造基类当基类构造完了才构造自己。
构造流程。
|-- main|-- ...|-- Derived::Derived()|-- Base::Base()|-- Base2::Base2()|-- Base3::Base3()构造流程汇编。
...
0x400b33: e8 34 02 00 00 callq 0x400d6c Derived::Derived()
...
0x400d83: e8 06 ff ff ff callq 400c8e Base::Base()
...
0x400d97: e8 20 ff ff ff callq 400cbc Base2::Base2()
...
0x400daf: e8 60 ff ff ff callq 400d14 Base3::Base3()构造 Base。 Vtable for Base
# _ZTV4Base: vtable for Base
Base::_ZTV4Base: 4u entries
0 (int (*)(...))0
# _ZTI4Base: typeinfo for Base
8 (int (*)(...))( _ZTI4Base)
16 (int (*)(...))Base::vBaseFunc
24 (int (*)(...))Base::vBaseFunc2Class Basesize24 align8base size24 base align8
Base (0x0x7fd19d5ee720) 0vptr(( Base::_ZTV4Base) 16u)构造 Base2。 Construction vtable for Base2 (0x0x7fd19d6aea90 instance) in Derived
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
Derived::_ZTC7Derived0_5Base2: 12u entries
0 64u
8 (int (*)(...))0
# _ZTI5Base2: typeinfo for Base2
16 (int (*)(...))( _ZTI5Base2)
24 (int (*)(...))Base2::vBaseFunc
32 (int (*)(...))Base2::vBase2Func
40 (int (*)(...))Base2::vBase2Func2
48 0u
56 18446744073709551552u # -64
64 (int (*)(...))-64
# _ZTI5Base2: typeinfo for Base2
72 (int (*)(...))( _ZTI5Base2)
# _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc()
80 (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv
88 (int (*)(...))Base::vBaseFunc2VTT for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTT7Derived: 7u entries
0 (( Derived::_ZTV7Derived) 24u)
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
8 (( Derived::_ZTC7Derived0_5Base2) 24u)
16 (( Derived::_ZTC7Derived0_5Base2) 80u)
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
24 (( Derived::_ZTC7Derived24_5Base3) 24u)
32 (( Derived::_ZTC7Derived24_5Base3) 80u)
40 (( Derived::_ZTV7Derived) 152u)
48 (( Derived::_ZTV7Derived) 96u)构造 Base3。 VTT for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTT7Derived: 7u entries
0 (( Derived::_ZTV7Derived) 24u)
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
8 (( Derived::_ZTC7Derived0_5Base2) 24u)
16 (( Derived::_ZTC7Derived0_5Base2) 80u)
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
24 (( Derived::_ZTC7Derived24_5Base3) 24u)
32 (( Derived::_ZTC7Derived24_5Base3) 80u)
40 (( Derived::_ZTV7Derived) 152u)
48 (( Derived::_ZTV7Derived) 96u)Construction vtable for Base3 (0x0x7fd19d6aeaf8 instance) in Derived
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
Derived::_ZTC7Derived24_5Base3: 12u entries
0 40u
8 (int (*)(...))0
# _ZTI5Base3: typeinfo for Base3
16 (int (*)(...))( _ZTI5Base3)
24 (int (*)(...))Base3::vBaseFunc2
32 (int (*)(...))Base3::vBase3Func
40 (int (*)(...))Base3::vBase3Func2
48 18446744073709551576u # -40
56 0u
64 (int (*)(...))-40
# _ZTI5Base3: typeinfo for Base3
72 (int (*)(...))( _ZTI5Base3)
80 (int (*)(...))Base::vBaseFunc
# _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2()
88 (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2Ev构造 Derived 参考上面 整体布局。 2.3.3. 虚函数调用
int main() {auto d new Derived;std::cout d std::endl;auto b static_castBase3 *(d);std::cout b std::endl;b-vBase3Func2();return 0;
}// 输出
// 0x9fa010
// 0x9fa028
// Derived::vBase3Func2b 指针指向存储 vptr.base3 的地址从 Derived 对象内存顶部向高地址偏移 0x18 个字节。vptr.base3 指针指向的虚表地址向高地址偏移 0x10 个字节获得 non-virtual thunk to Derived::vBase3Func2() 函数地址。通过 non-virtual thunk to Derived::vBase3Func2() 地址跳转到 Derived::vBase3Func2 虚函数获取虚表上对应的虚函数进行调用。 3. 后记 要理解多态的对象内存布局要注意理解多个虚指针是如何根据不同的基类指针进行偏移的当虚指针指向虚表后要获得对应的虚函数虚指针要偏移一定的位置才能定位到对应的虚表上的虚函数。 如果要用一个词来形容多态那就是 覆盖派生类重写基类虚函数像图层一样派生类上层覆盖下层基类层层叠加最后得出了被覆盖的结果这也是我们理解 虚表 结构的核心思维方式。 关于有继承关系的 C 多态探索因为本人水平有限以上只作了一些基础简单的 Demo 的分析还有一些应用场景没有涉及例如 虚析构。 很多技术细节没有在文章中提及有兴趣的朋友可以动手写写 demo 用 gdb 调试一下查看对象内存布局上的地址数据以及反汇编查看对象构造的逻辑是否与自己的理解一致这样才能在不断变化的问题表象里寻获答案本质。