C++对象模型探索-1

  • 底层实现
  • 深度探索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
34
35
36
37
38
39
40
class A
{
public:
};
A a;
//sizeof(A)=sizeof(a)=1;

class A
{
public:
void func() {};
void func1() {};
void func2() {};
};
A a;
sizeof(A)=sizeof(a)=1;

class A
{
public:
void func() {};
void func1() {};
void func2() {};

char num;
};
//sizeof(a)=1; 1 为char num的内存空间
//&a=&a.num
//a地址中的数为q

class A
{
public:
void func() {};
void func1() {};
void func2() {};

int num;
};
//sizeof(a)=4; 4 为int num的内存空间
  • 类成员函数不占用类对象的内存空间
  • 空类大小为1个字节,空类存在首地址
  • 成员变量包含在每个对象中,占用类对象的内存空间
  • 成员函数 跟着类走,不是类对象,不管类产生了多少个类对象

2. 对象结构的发展和演化

  • 非静态的成员变量(普通成员变量)跟着类对象走(存在类对象内部),每个类对象都有自己的成员变量
  • 静态成员变量和对象没关系,不会保存在类对象内部,保存在对象外面
1
2
3
4
5
6
7
8
class A
{
public:
int num;
static int sa;
static int sb;
};
sizeof(A)=4=sizeof(a.num)
  • 成员函数无论静态函数非静态保存在类对象外部,都不占用类对象
  • 存在虚函数,类对象多占4个字节
1
2
3
4
5
6
7
8
9
10
class A
{
public:
int num;
static int sa;
static int sb;
virtual void func1() {}
virtual void func2() {}
};
sizeof(a)=4+4=8;

虚函数

  • 类里只要有一个虚函数,这个类A产生一个指向虚函数表的指针,四个字节
  • 一个虚函数对应一个指针,这些指针放到虚函数表里 virtual table VTBL
  • 虚函数表基于类,而不是类对象
  • 类对象。虚函数存在,系统在类对象中添加了一个 指针VPTR,指向虚函数表
  • VPTR由编译器适当时机添加(比如 构造函数)

  • 静态数据成员不计算在类对象的sizeof中
  • 普通成员函数和静态成员函数不计算在类对象的sizeof中
  • 虚函数不计算在类对象的sizeof中,但是虚函数会让类对象sizeof增加4个字节,容纳虚函数表指针。
  • VTBL基于类,和对象没有关系,不是基于对象的
  • 如果有多个数据成员,为了提高访问速度,某些编译器可能将数据成员之间的内存占用比例进行调整,存在内存字节对齐问题。
1
2
3
4
5
class A{
char ch;
int integer;
};
sizeof(a)=8;//4个字节对齐
  • 不管什么类型指针,这个指针的占用内存固定 比如4个字节
    • 非静态成员变量和内存对齐
    • 虚函数表指针

3. This指针调整

  • 存在多重继承
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
class A {
public:
int a;
A() {
printf("&A= %p \n",this);
}
void funcA()
{
printf("&A= %p \n", this);
}
static int sa;
};

class B {
public:
int b;
B() {
printf("&B= %p \n", this);
}
void funcB()
{
printf("&B= %p \n", this);
}
};

class C :public A,public B{
public:
int c;
C()
{
printf("&C= %p \n", this);
}
void funcC()
{
printf("&C= %p \n", this);
}
};
C myc;
myc.funcA();//
myc.funcB();//
myc.funcC();//
//&A= 006FFC44
//&B= 006FFC48
//&C= 006FFC44
//&A= 006FFC44
//&B= 006FFC48
//&C= 006FFC44
//&C=&A
  • 派生类对象是包含基类子对象的

  • 如果派生类只从一个类派生的,派生类对象地址和基类子对象的地址相同

  • 如果继承多个,第一个基类子对象的开始地址和派生类对象开始地址相同,后续这类基类子对象的开始地址和派生类对象的开始地址相差多少

    • 把第一个基类子对象所占用的地址
  • 如果C类覆盖B中方法,C中调用B中方法时,this指针不变

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
class A {
public:
int a;
A() {
printf("&A= %p \n",this);
}
void funcA()
{
printf("&A= %p \n", this);
}
static int sa;
};

class B {
public:
int b;
B() {
printf("&B= %p \n", this);
}
void funcB()
{
printf("&B= %p \n", this);
}
};

class C :public A,public B{
public:
int c;
C()
{
printf("&C= %p \n", this);
}
void funcC()
{
printf("&C= %p \n", this);
}
void funcB()
{
printf("&C B= %p \n", this);
}
};
C myc;
myc.funcA();
myc.funcB();
myc.B::funcB();
myc.funcC();
//0
//4
//0

//0
//0
//4
//0

  • 调用哪个子类的成员函数,这个this指针就会被调整到对象子类的开始地址

4. 分析obj,构造函数语义

  • 默认构造函数,没有参数的构造函数
  • 如果没有定义任何构造函数,编译器就会隐式提供默认构造函数,即合成的默认构造函数
  • 合成的默认构造函数,只有在必要的时候,编译器才会为我们提供,而不是必然
  • 每个CPP编译生成一个.obj(.o)文件 linux -c,最终把很多obj文件链接到一起生成exe文件
1
2
3
4
5
6
7
8
9
10
11
12
class MATXP {
public:
};
class MBTX {
public:
int m;
int n;
void func()
{
cout << "MBTXTEST"<< endl;
}
};
  • VS2015 开发人员命令 dumpbin /all main.obj > main.txt
  • main.txt COFF格式,通用对象文件格式
  • main.txt没有MBTX::MBTX 默认构造函数

  • 合成构造函数
  • Condition1:
    • *该MBTX类没有任何构造函数,但包含一个类对象成员,而该对象所属的类有一个缺省的构造函数,为了调用MATX的默认构造函数,为MBTX生成一个合成默认构造函数 *
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MATX {
public:
MATX() {

cout <<"MATX" << endl;
}
};
class MBTX {
public:
int m;
int n;
MATX ma;
void func()
{
cout << "MBTXTEST"<< endl;
}
};
1
2
3
4
5
6
                                               Symbol    Symbol
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- ------
0000002A REL32 00000000 8D ??0MATX@@QAE@XZ (public: __thiscall MATX::MATX(void))
00000035 REL32 00000000 90 ??0M0TX@@QAE@XZ (public: __thiscall M0TX::M0TX(void))
00000048 REL32 00000000 9A __RTC_CheckEsp
  • 先定义MATX,先调用MATX构造函数

  • Condition2

    • 一个父类带缺省构造函数,子类没有任何构造函数,这个缺省的构造函数要被调用,编译器会为这个子类合成出一个默认构造函数,合成目的,为了调用父类的构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MTX_BASE {
public:
MTX_BASE()
{
cout <<"MTX_BASE" << endl;
}
};
class MTX_CHILD :public MTX_BASE{
public:
int m_i;
int m_j;
void func()
{

}
};
1
2
3
4
5
60501020 flags
Code
COMDAT; sym= "public: __thiscall MTX_CHILD::MTX_CHILD(void)" (??0MTX_CHILD@@QAE@XZ)
16 byte align
Execute Read
  • Condition3:
    • 如果一个类有虚函数,没有构造函数时
    • 虚函数存在,编译器生成虚函数表VTBL,在合成构造函数里,把类的虚函数表地址,赋值给类对象的虚函数表指针。(赋值语句)
    • MTX_CHILD2有父类,有缺省构造函数
      • 生成MTX_CHILD2的虚函数表vftable
      • 调用父类的构造函数
      • 将MTX_CHILD2类的虚函数表地址赋值给MTX_CHILD2类对象的虚函数表指针
1
2
3
4
5
6
class MTX_CHILD2 {
public:
virtual void func1() {
cout <<"MTX_CHILD2" << endl;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
60501020 flags
Code
COMDAT; sym= "public: __thiscall MTX_CHILD2::MTX_CHILD2(void)" (??0MTX_CHILD2@@QAE@XZ)
16 byte align
Execute Read

RAW DATA #9
00000000: 55 8B EC 81 EC CC 00 00 00 53 56 57 51 8D BD 34 U.¨¬.¨¬¨¬...SVWQ.?4
00000010: FF FF FF B9 33 00 00 00 B8 CC CC CC CC F3 AB 59 ???13...?¨¬¨¬¨¬¨¬¨®?Y
00000020: 89 4D F8 8B 45 F8 C7 00 00 00 00 00 8B 45 F8 5F .M?.E??......E?_
00000030: 5E 5B 8B E5 5D C3 ^[.?]?

RELOCATIONS #9
Symbol Symbol
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- ------
00000028 DIR32 00000000 C2 ??_7MTX_CHILD2@@6B@ (const MTX_CHILD2::`vftable')//赋值
1
2
3
4
5
6
7
8
9
10
11
class MTX_CHILD2:public MTX_BASE {
public:
int m_i;
virtual void func1() {
cout <<"MTX_CHILD2" << endl;
}
MTX_CHILD2()
{
m_i = 15;
}
};
1
2
3
4
5
Offset    Type              Applied To         Index     Name
-------- ---------------- ----------------- -------- ------
0000002A REL32 00000000 8E ??0MTX_BASE@@QAE@XZ (public: __thiscall MTX_BASE::MTX_BASE(void))
00000033 DIR32 00000000 CD ??_7MTX_CHILD2@@6B@ (const MTX_CHILD2::`vftable')
00000050 REL32 00000000 9B __RTC_CheckEsp
  • Condition4
    • 一个类带有虚基类,编译器也会合成默认构造函数
    • 虚基类,通过两个直接基类继承同一个间接基类。
    • vbtable 虚基类表 vftable虚函数表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class GRAND {
public:

};
class PARENT1 :virtual public GRAND {
public:

};
class PARENT2 :virtual public GRAND {
public:
};

class CHILD :public PARENT1, public PARENT2 {
public:
};
1
2
3
4
5
6
7
8
9
10
11
12
60501020 flags
Code
COMDAT; sym= "public: __thiscall CHILD::CHILD(void)" (??0CHILD@@QAE@XZ)
16 byte align
Execute Read
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- ------
0000002E DIR32 00000000 49 ??_8CHILD@@7BPARENT1@@@ (const CHILD::`vbtable'{for `PARENT1'})
00000038 DIR32 00000000 4D ??_8CHILD@@7BPARENT2@@@ (const CHILD::`vbtable'{for `PARENT2'})
00000042 REL32 00000000 32 ??0PARENT1@@QAE@XZ (public: __thiscall PARENT1::PARENT1(void))
0000004F REL32 00000000 33 ??0PARENT2@@QAE@XZ (public: __thiscall PARENT2::PARENT2(void))
00000062 REL32 00000000 38 __RTC_CheckEsp

7. 拷贝构造函数语义

  • 传统上,没有定义一个拷贝构造,编译器会在必要时,提供合成的拷贝构造函数
1
2
3
4
5
6
7
class A {
public:
int m_num;
};
A mya1;
mya1.m_num = 13;
A mya2 = mya1;
  • 上述没有合成拷贝构造函数,编译器内部成员变量初始化,直接按值拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class B {
public:
int mb_num;
};

class A {
public:
B b;
int ma_num;
};

A mya1;
mya1.ma_num = 13;
mya1.b.mb_num = 7;
A mya2 = mya1;
  • 上述没有合成拷贝构造函数
  • A mya2 = mya1; 拷贝构造一个对象,编译器直接拷贝数据,如果A中有类对象类型变量,类似递归式拷贝类B中成员变量。

编译器合成拷贝构造函数,什么时候,干什么

  • Condition1
    • 类A没有拷贝构造,含有一个类对象类型的成员变量,其有拷贝构造函数,当代码有涉及A的拷贝构造时
    • B也需要默认构造
    • 编译器合成的拷贝构造只干一些特殊的事,如果只是类似成员变量拷贝,编译器不合成拷贝构造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class B {
public:
int mb_num;
B(const B& B_)
{
cout <<"B copy construct" << endl;
}
B()
{
}
};

class A {
public:
B b;
int ma_num;
};

A mya1;
mya1.ma_num = 13;
mya1.b.mb_num = 7;
A mya2 = mya1;//去掉就没有 合成拷贝构造
1
2
3
4
5
60501020 flags
Code
COMDAT; sym= "public: __thiscall A::A(class A const &)" (??0A@@QAE@ABV0@@Z)
16 byte align
Execute Read
  • Condition2
    • 如果一个类没有拷贝构造,父类有拷贝构造,当代码中又涉及到类的拷贝构造时,编译器合成拷贝构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class B {
public:
int mb_num;
B(const B& B_)
{
cout <<"B copy construct" << endl;
}
B()
{
}
};
class B_SON:public B
{
public:


};
B_SON b;
b.mb_num = 1;
B_SON b2 = b;
1
2
3
4
5
60501020 flags
Code
COMDAT; sym= "public: __thiscall B_SON::B_SON(class B_SON const &)" (??0B_SON@@QAE@ABV0@@Z)
16 byte align
Execute Read
  • Condition3
    • 类B没有拷贝构造,继承或声明了虚函数,当代码中又涉及到类的拷贝构造时
    • 在拷贝构造函数里,将类虚函数表地址 赋值给类对象虚函数表指针
1
2
3
4
5
6
7
8
9
class B_SON
{
public:
virtual void func()
{
cout <<"B_SON" << endl;
}

};
1
2
3
4
5
6
7
8
9
10
11
60501020 flags
Code
COMDAT; sym= "public: __thiscall B_SON::B_SON(class B_SON const &)" (??0B_SON@@QAE@ABV0@@Z)
16 byte align
Execute Read

RELOCATIONS #9
Symbol Symbol
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- ------
00000028 DIR32 00000000 CA ??_7B_SON@@6B@ (const B_SON::`vftable')
  • Condition4
    • 没有拷贝构造,该类有虚基类,涉及到类对象的拷贝构造时
    • GRAND没有合成拷贝构造,PARENT1,PARENT2 CHILD合成拷贝构造
1
2
3
4
5
6
7
8
9
10
11
12
13
class GRAND {
public:

};
class PARENT1:virtual public GRAND {
public:
};
class PARENT2 :virtual public GRAND {
public:
};
class CHILD : public PARENT1,public PARENT2 {
public:
};
1
2
3
4
5
6
7
8
RELOCATIONS #4
Symbol Symbol
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- ------
0000002E DIR32 00000000 5F ??_8CHILD@@7BPARENT1@@@ (const CHILD::`vbtable'{for `PARENT1'})
00000038 DIR32 00000000 63 ??_8CHILD@@7BPARENT2@@@ (const CHILD::`vbtable'{for `PARENT2'})
00000069 REL32 00000000 45 ??0PARENT1@@QAE@ABV0@@Z (public: __thiscall PARENT1::PARENT1(class PARENT1 const &))
0000009B REL32 00000000 47 ??0PARENT2@@QAE@ABV0@@Z (public: __thiscall PARENT2::PARENT2(class PARENT2 const &))

8. 程序转换语义

  • 代码会被编译器拆分,编译器更容易理解和实现
  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
class X {
public:
int m_i;
X()
{
m_i = 0;
cout << "construct" << endl;
}
X(const X& X_)
{
cout <<"copy construct" <<endl;
}
};
X x0;
x0.m_i = 2;
X x1 = x0;
X x2(x0);
X x3=(x0);

/*
X x100=(x0); 编译器拆分两部
X x100; 1.定义对象,分配内存,没有调用构造函数
x100::X::X(x0); 2. 调用对象拷贝构造
*/
  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
    34
    35
    36
    class X {
    public:
    int m_i;
    X()
    {
    m_i = 0;
    cout << "construct" << endl;
    }
    X(const X& X_)
    {
    cout <<"copy construct" <<endl;
    }
    ~X()
    {
    cout << "deconstruct" << endl;
    }
    };
    void func(X x)
    {
    return ;
    }
    X x0;
    func(x0);
    /*
    construct
    copy construct
    deconstruct
    deconstruct

    老编译器
    X tempobj;// 1.一个临时对象
    tempobj.X::X(x0); 2. 临时对象拷贝构造
    func(tempobj) 3.
    func(X& temoobj) 4. 参数变为引用
    tempobj.X::~X() 5. 析构
    */
  2. 返回值初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    X func2()
    {
    X x0;
    return x0;
    }
    X my=func();
    /*
    construct
    copy construct
    deconstruct
    deconstruct
    */
    /*
    编译器
    X my; //1 生成对象,不调用构造函数
    func2(my);
    void func2(X& my)
    {
    X x0;// 不调用构造函数
    my.X::X(x0);// 拷贝构造
    }
    */
1
2
3
4
5
6
7
8
9
10
11
12
13
func2().func_test();
/*
编译器

X my; //1 生成对象,不调用构造函数
func2(my);
void func2(x& my)
{
X x0;// 不调用构造函数
my.X::X(x0);// 拷贝构造
}
(func2(my),my).func_test()//逗号表达式,
*/
1
2
3
4
5
6
7
8
9
10
X (*pf) ();//函数指针
pf ()= func2();
pf().func_test();
/*
X my;
void (*pf)(X&);// 多一个引用参数
pf=func2;
pf(my);
my.functest();
*/

9. 程序的优化

  • 开发者VS 编译器
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 CTempValue
{
public:
int val1;
int val2;
public:
CTempValue(int v1 = 0, int v2 = 0)
:val1(v1), val2(v2) {
cout <<"construct" << endl;
}
CTempValue(const CTempValue& t)
:val1(t.val1), val2(t.val2)
{
cout << "copy construct"<< endl;
}
virtual ~CTempValue()
{
cout << "deconstruct" << endl;
}
};

CTempValue Double(CTempValue & t)
{
CTempValue tempm;//一个构造 一个析构
tempm.val1 = t.val1;
tempm.val2 = t.val2;
return tempm;//一个临时对象, 拷贝构造 返回临时对象,一个析构
}
1
2
3
4
5
6
7
8
9
CTempValue ts1(10, 20);
Double(ts1);
/*
construct
construct
copy construct
deconstruct
deconstruct
*/

开发者优化

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
CTempValue Double(CTempValue & t)
{
//CTempValue tempm;//一个构造 一个析构
//tempm.val1 = t.val1*2;
//tempm.val2 = t.val2*2;
//return tempm;//一个临时对象, 拷贝构造 返回临时对象,一个析构
return CTempValue(t.val1*2,t.val2*2);
}
CTempValue ts1(10, 20);
Double(ts1);
/*
construct
construct//构造对象
deconstruct// -----------这个析构 因为Double返回的对象没有人接
*/

/*
编译器
void Double(CTempValue& temp,CTempValue& ts1)
{
temp.CTempValue::CTempValue(ts1.val1,ts1.val2);
return ;
}

CTempValue ts1;
ts1.CTempValue::CTempValue(10,20);
CTempValue tempobj;
Double(tempboj,ts1);
*/

编译器优化

  • GCC 编译器优化 针对于返回临时对象NRV 优化 named return value

  • 1
    g++ -fno-elide-constructors 2_8.cpp -o 2_8//禁止优化
  • 编译器是否优化,需要测试

  • 编译器优化 有可能出错

10. 程序优化续

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
class X {
public:
int m_i;
X(int value)
:m_i(value)
{
cout <<"construct(value)" << endl;
}
X()
{
m_i = 0;
cout << "construct" << endl;
}
X(const X& t)
:m_i(t.m_i)
{
cout << "copy construct" << endl;
}
~X()
{
cout << "deconstruct" << endl;
}
};

X x10(1000);//编译器不优化时 最优
cout << string(20,'-')<< endl;
X x11=1000;///转换函数
cout << string(20, '-') << endl;
X x12=X(1000);
cout << string(20, '-') << endl;
X x13 = (X)1000;
cout << string(20, '-') << endl;
/*
construct(value)
--------------------
construct(value)
--------------------
construct(value)
--------------------
construct(value)
--------------------
*/

/*
编译器 不优化
X x10;
x10.X::X(1000);//最优

X temp;
temp.X::X(1000)// 一个参数的构造函数
X X12;
x12.X::X(temp)//拷贝构造函数调用
temp.X::`X() //析构函数
*/
  • 编译器面临用一个类对象作为另一个类对初值的情况,各个编译器表现不同,没办法确定编译器一定调用拷贝构造
  • 拷贝构造不一定必须有,视情况而定
  • 简单成员变量类型,int,double,不需要拷贝构造,编译器内部本身支持成员变量的bitwise(按位拷贝)
1
2
3
4
5
6
//X的拷贝构造 注释掉

X x0;
x0.m_i = 150;
X x1(x0);//调用拷贝构造
cout <<x1.m_i << endl;//编译器支持bitwise拷贝
  • 当需要处理复杂的成员变量类型时候,指针成员
1
2
3
4
5
6
7
8
9
10
11
12
13
X(const X& t)
//:m_i(t.m_i)
{
// m_i = t.m_i;
cout << "copy construct" << endl;
}
X x0;
x0.m_i = 150;
X x1(x0);//调用拷贝构造
cout <<x1.m_i << endl;//编译器支持bitwise拷贝 =====0
/*
当增加了自己的拷贝构造时,编译器的bitwise拷贝 失效,需要自己对成员变量的初始化负责;
*/

11 成员初始化列表

  • 何时必须用初始化列表

    • 如果这个成员是引用变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class A {
    public:
    int m_x;
    int m_y;
    int &m_yy;
    A(int & tempvalue)
    :m_x(0),m_y(0),m_yy(tempvalue)
    {

    }

    };
    • 如果类成员是const类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    class A {
    public:
    int m_x;
    int m_y;
    int &m_yy;
    const int m_c;
    A(int & tempvalue)
    :m_x(0),m_y(0),m_yy(tempvalue),m_c(tempvalue)
    {

    }

    };
    • 基类有构造函数,而且基类构造函数有参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class B
    {
    public:
    int ba;
    int bb;
    B(int tmpa, int tmpb)
    {}
    };
    class A:public B
    {
    public:
    int m_x;
    int m_y;
    int &m_yy;
    const int m_c;
    A(int & tempvalue)
    :m_x(0),m_y(0),m_yy(tempvalue),m_c(tempvalue),B(tempvalue,tempvalue)
    {

    }

    };
    • 成员变量类型为类类型,而类的构造函数有参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Sub
    {

    public:
    Sub(int x)
    {
    }
    };
    class A:public B
    {
    public:
    int m_x;
    int m_y;
    int &m_yy;
    const int m_c;
    Sub sub;
    A(int & tempvalue)
    :m_x(0),m_y(0),m_yy(tempvalue),m_c(tempvalue),B(tempvalue,tempvalue),sub(tempvalue)
    {

    }

    };
  • 使用初始化列表的优势

    • 提高程序运行效率
    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

    class X {
    public:
    int m_i;
    X(int val = 0)//类型转换构造函数
    :m_i(val)
    {
    printf("this =%p \n",this);
    cout <<"X(INT ) construct" << endl;
    }
    X(const X& t)
    {
    printf("this =%p \n", this);
    cout << "copy construct" << endl;
    }
    X& operator=(const X& t)
    {
    printf("this =%p \n", this);
    cout << "copy operator=" << endl;
    return *this;
    }
    ~X()
    {
    printf("this =%p \n", this);
    cout << "deconstruct" << endl;
    }
    };

    class A {
    public:
    X obj;
    int m_test;
    A(int temp)// 构造 X obj 耗费了一次调用构造函数的机会
    // 编译器 X obj; obj.X::X();
    {
    obj = 1000;// 临时对象构造 拷贝构造 析构
    m_test = 500;
    /*
    编译器
    X temp;
    temp.X::X(1000) //构造函数
    obj.X::operator=(temp) //赋值运算
    temp.X::~X()//析构
    */
    }
    };
    A a(1000);
    /*
    this =004FFB1C X(int ) construct
    this =004FF960 X(int ) construct
    this =004FFB1C copy operator=
    this =004FF960 deconstruct

    */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class A {
public:
X obj;
int m_test;
A(int temp)// 构造 X
:obj(1000)
/*
X obj;
obj.X::X(1000);
*/
{
// obj = 1000;// 临时对象构造 拷贝构造 析构
m_test = 500;
}
};

/*
this =00CFFD3C X(int ) construct
*/
  • 将初始化列表看作函数体中一部分
  • 对于类对象成员放到初始化成员列表中,效果明显
  • 细节探究

    • 初始化列表中代码,可以看作是被编译器安插到构造函数体中
    • 初始化列表中代码 在任何用户自己的构造函数体之前执行的
    • 变量的初始化顺序是定义顺序,不是初始化列表中的调用顺序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class X {
    public:
    int m_i;
    int m_i2;
    X(int val = 0)//类型转换构造函数
    :m_i2(val),m_i(m_i2) //出错m_i 先执行
    {
    printf("this =%p ",this);
    cout <<"X(int ) construct" << endl;
    }
  • 不建议 初始化列表中成员变量的赋值

12 虚函数表指针位置分析

  • 类中有虚函数,该类会产生一个虚函数表,类对象有一个虚函数表指针VPTR,类似成员变量,占4个字节,指向虚函数表的开始地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class A {
public:
int i;
static int s_i;
virtual void testfunc()
{
}
};

A a;
int len = sizeof(a);
char *p1 = reinterpret_cast<char*> (&a);
char *p2= reinterpret_cast<char*> (&(a.i));


if (p1 == p2)// a.i 和 a 地址相同, i在 虚函数表指针的上面
{
cout << p1<< endl;
}
  • 虚函数表指针 位于对象内存的头部

13 继承关系下的虚函数的手工调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {

public:
virtual void f(){ cout << "base f"<< endl; }
virtual void g() { cout << "base g" << endl; }

virtual void h()
{
cout << "base h" << endl;
}
};

class Derive :public Base {
virtual void g() { cout << "Derive g"<< endl; }
};
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
57
58
59
60
	cout << sizeof(Base)<< endl;
cout << sizeof(Derive) << endl;
Derive *d = new Derive();
long *pvptr = (long*)d;//指向对象的指针d 0x00d45dd0
long * vptr = (long*)(*pvptr);//(*pvptr)表示pvptr指向的对象 Derive对象本身 四个字节,代表虚函数表指针首地址
//(*pvptr)=0x002B9B60

for (int i = 0; i <= 4; i++)
{
printf("vptr[%d]=0x:%p \n",i,vptr[i]);// 肯定越界
}
typedef void(*Func)(void);

Func f = (Func)vptr[0];
Func g = (Func)vptr[1];
Func h = (Func)vptr[2];

f();
g();
h();

Base *b = new Base();
long *pvptr2 = (long*)b;
long *vptr2 = (long*)(*pvptr2);

for (int i = 0; i <= 4; i++)
{
printf("vptr2[%d]=0x:%p \n", i, vptr2[i]);// 肯定越界
}

Func f2 = (Func)vptr2[0];
Func g2 = (Func)vptr2[1];
Func h2= (Func)vptr2[2];

f2();
g2();
h2();

/*
4
4
vptr= 00499B60
vptr[0]=0x:0049108C
vptr[1]=0x:00491091
vptr[2]=0x:004910E1
vptr[3]=0x:00000000
vptr[4]=0x:69726544
base f
Derive g
base h
vptr2= 00499B34
vptr2[0]=0x:0049108C
vptr2[1]=0x:00491249
vptr2[2]=0x:004910E1
vptr2[3]=0x:00000000
vptr2[4]=0x:65736162
base f
base g
base h
*/

14 虚函数表分析

1563259723100

  • 一个类只有包含虚函数才会存在虚函数表,同属于一个类的对象共享虚函数表,但是有各自的vptr(虚函数表指针),当然所指向的地址(虚函数表首地址)相同。
  • 父类中有虚函数,等于子类中有虚函数。父类中有虚函数表,子类中也有虚函数表,如果在子类中把父类中的虚函数去掉virtual,子类中仍然是虚函数。
  • 但不管是子类还是父类,都只会有一个虚函数表,不能认为子类中有一个虚函数表+父类中有一个虚函数表,不能认为子类中有两个虚函数表
  • 如果子类中没有新的虚函数,可以认为子类和父类的虚函数表内容相同,仅仅内容相同,在内存中处于不同位置,是内容相同的两张表,虚函数表中每一项保存着一个虚函数的首地址,但如果子类的虚函数表表项和父类的虚函数表某项代表同一个函数(表示子类没有覆盖父类的虚函数,则该表项指向的该函数地址相同)
  • 超出虚函数表部分不可知
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
typedef void(*Func)(void);
Derive derive;
long *pvptr_derive = (long*)(&derive);//pvptr_derive = 0x001efbec {0x00c69b60}
long * vptrderive = (long*)(*pvptr_derive);//vptrderive = 0x00c69b60 {class13_virtual_call_with_inherit.exe!const Derive::`vftable'} {0x00c6108c}
Func f1 = (Func)vptrderive[0];//f1 = 0x00c6108c {class13_virtual_call_with_inherit.exe!Base::f(void)}
Func g1=(Func)vptrderive[1];//g1 = 0x00c61091 {class13_virtual_call_with_inherit.exe!Derive::g(void)}
Func h1 = (Func)vptrderive[2];
Func i1 = (Func)vptrderive[3];
Func j1 = (Func)vptrderive[4];

Derive derive2 = derive;
long *pvptr_derive2 = (long*)(&derive2);
long * vptr_derive2 = (long*)(*pvptr_derive2);

Base base = derive;//子类赋值给父类,子类中属于父类的内容会被编译器自动区分(切割)出来,并拷贝给父类对象
/*
生成base对象,并用derive对象初始化base对象,derive的虚函数表指针并没有覆盖base对象的虚函数表指针值
*/

long *pvptr_base = (long*)(&base);//pvptr_base = 0x001efb68 {0x00c69b34}
long * vptr_base = (long*)(*pvptr_base);//vptr_base = 0x00c69b34 {class13_virtual_call_with_inherit.exe!const Base::`vftable'} {0x00c6108c}


Func bf1 = (Func)vptr_base[0];//bf1 = 0x00c6108c {class13_virtual_call_with_inherit.exe!Base::f(void)}
Func bg1 = (Func)vptr_base[1];//bg1 = 0x00c61249 {class13_virtual_call_with_inherit.exe!Base::g(void)}
Func bh1 = (Func)vptr_base[2];//bh1 = 0x00c610e1 {class13_virtual_call_with_inherit.exe!Base::h(void)}
Func bi1 = (Func)vptr_base[3];
Func bj1 = (Func)vptr_base[4];
  • OO面向对象,OB基于对象
  • C++通过类的指针和引用来实现多态,这就是面向对象
  • OB,也叫ADT,抽象数据模型,不支持多态。执行速度快,因为函数调用的解析不需要运行时决定,编译期间就已经完成,空间更紧凑
  • C++既支持OO,又支持OB

15 多重继承的虚函数表分析

  • 多重继承的虚函数表
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 Base1 {
public:
virtual void f() { cout << "base1::f()"<< endl; }
virtual void g() { cout << "base1::g()" << endl; }
};
class Base2
{
public:
virtual void h() { cout << "base2::h()" << endl; }
virtual void i() { cout << "base2::i()" << endl; }
};
class Derived:public Base1,public Base2 {
public:
virtual void f() { cout << "Derived::f()" << endl; }

virtual void i() { cout << "Derived::i()" << endl; }


virtual void mh() { cout << "Derived::mh()" << endl; }

virtual void mi() { cout << "Derived::mi()" << endl; }

virtual void mj() { cout << "Derived::mj()" << endl; }
};
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
cout << "base1: " << sizeof(Base1) << endl;
cout << "base2: " << sizeof(Base2) << endl;
cout << "Derived: " << sizeof(Derived) << endl;//两个 虚函数表指针

Derived ins;
Base1 &b1 = ins;
Base2 &b2 = ins;
Derived& d = ins;

typedef void(*Func)(void);
long * pderived1 = (long*)(&ins);//pderived1 = 0x00b4f9f4 {0x00a39b94}
long *vptr1 = (long*)(*pderived1); //第一个虚函数表指针vptr1 = 0x00a39b94 {class14_multi_inherit.exe!const Derived::`vftable'{for `Base1'}} {0x00a31271}
long * pderived2 = pderived1 + 1;//pderived2 = 0x00b4f9f8 {0x00a39bb0}

long *vptr2 = (long*)(*pderived2);//vptr2 = 0x00a39bb0 {class14_multi_inherit.exe!const Derived::`vftable'{for `Base2'}} {0x00a313e3}

Func f1 = (Func)vptr1[0];//f1 = 0x00a31271 {class14_multi_inherit.exe!Derived::f(void)}
Func f2 = (Func)vptr1[1];//f2 = 0x00a3125d {class14_multi_inherit.exe!Base1::g(void)}
Func f3 = (Func)vptr1[2];//f3 = 0x00a311bd {class14_multi_inherit.exe!Derived::mh(void)}
Func f4 = (Func)vptr1[3];//f4 = 0x00a312ee {class14_multi_inherit.exe!Derived::mi(void)}
Func f5 = (Func)vptr1[4];//f5 = 0x00a3112c {class14_multi_inherit.exe!Derived::mj(void)}


Func f11 = (Func)vptr2[0];//f11 = 0x00a313e3 {class14_multi_inherit.exe!Base2::h(void)}
Func f21= (Func)vptr2[1];//f21 = 0x00a311f9 {class14_multi_inherit.exe!Derived::i(void)}
Func f31 = (Func)vptr2[2];//f31 = 0x00000000
Func f41 = (Func)vptr2[3];
Func f51 = (Func)vptr2[4];
  • 一个对象,如果有多个基类,则该子类有多个虚函数表指针,一个虚函数表,对应各个基类中的vptr按继承顺序依次放置在类的内存空间中,且子类与第一个基类公用一个vptr(第二个基类有自己的vptr)

  • 适合VS2015 ,不适合GCC linux

    • 1563275212420
  • 子类对象ins有两个虚函数表指针,

  • 类Derived有两个虚函数表

  • 子类和第一个基类共用一个vptr,(vptr指向一个虚函数表,所以也可以说子类和第一个基类公用一个虚函数表vtbl),类Derived的虚函数表1中的5个函数,g正好为Base1中函数

  • 子类中虚函数覆盖了父类中的同名虚函数

16 辅助工具、vpt、vtbl创建时机

  • 开发者命令行窗口 cl.exe

  • “第一”

  • 1
    cl /d1 reportSingleClassLayoutDerived main.cpp
  • 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 Derived   size(8):
    +---
    0 | +--- (base class Base1)
    0 | | {vfptr}
    | +---
    4 | +--- (base class Base2)
    4 | | {vfptr}
    | +---
    +---

    Derived::$vftable@Base1@:
    | &Derived_meta
    | 0
    0 | &Derived::f
    1 | &Base1::g
    2 | &Derived::mh
    3 | &Derived::mi
    4 | &Derived::mj

    Derived::$vftable@Base2@:
    | -4
    0 | &Base2::h
    1 | &Derived::i

    Derived::f this adjustor: 0
    Derived::i this adjustor: 4
    Derived::mh this adjustor: 0
    Derived::mi this adjustor: 0
    Derived::mj this adjustor: 0
  • 1
    2
    g++ -fdump-class-hierarchy -fsyntax-only main.cpp
    //生成.class文件
  • vptr什么时候创建的,vptr跟着对象走,对象创建的时候(运行时)

  • 对于有虚函数的类,编译器在构造函数中添加代码,为vptr赋值的代码,(在编译期间)

  • 创建对象时,执行构造函数,赋值vptr,可看作成员变量


  • 虚函数表是编译器在编译期间,(obj .o文件)为每个类确定了对象的虚函数表中的内容,在编译期间添加给vptr赋值的代码,调用构造函数,

B3mENrq

17 单纯的类不纯时引发的虚函数调用问题

  • 单纯的类: 不包含虚函数和虚基类
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 X {
public:
int x;
int y;
int z;
/*
X()
:x(0), y(0), z(0)
{
cout <<"X construct" << endl;
}*/
X()
{
cout << "X construct" << endl;
memset(this,0,sizeof(X));//OK
}
/*
X(const X& t)
:x(t.x), y(t.y), z(t.z)
{
cout << "X copy construct" << endl;
}*/
X(const X& t)
{
memcpy(this, &t, sizeof(X));//OK
cout << "X copy construct" << endl;
}
};
  • 如果类不单纯,使用memset,memcpy出现崩溃
  • 不单纯,某些情况下,编译器在类内中增加些代码,某些隐藏成员变量。这种隐藏成员变量的增加(使用)赋值,均在执行构造函数或拷贝构造函数之前进行。如果使用memset memcpy使得编译器给隐藏变量的值清空或覆盖
  • 比如增加虚函数,编译器增加虚函数表指针(隐藏的成员变量)
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
57
58
59
60
61
62
63
64
class X {
public:
int x;
int y;
int z;
/*
X()
:x(0), y(0), z(0)
{
cout <<"X construct" << endl;
}*/

X()
{
//vptr=vtbl// memset将vptr清零
cout << "X construct" << endl;
memset(this,0,sizeof(X));//OK
}
/*
X(const X& t)
:x(t.x), y(t.y), z(t.z)
{
cout << "X copy construct" << endl;
}*/
X(const X& t)
{
memcpy(this, &t, sizeof(X));//OK
cout << "X copy construct" << endl;
}
virtual ~X()
{
cout << "X deconstruct" << endl;
}
virtual void virtual_func()
{
cout << "X virtual func" << endl;
}
void func()
{
cout << "X func" << endl;
}
};

X x0;//__vfptr = 0x00000000 {???, ???}
x0.x = 100;
x0.y = 200;
x0.z = 300;

x0.virtual_func();//栈中创建对象,即使vptr=0依然可以调用虚函数

X x1(x0);//__vfptr = 0x00000000 {???, ???}
cout <<x1.x<<" "<<x1.y<<" "<<x1.z << endl;

X* px0 = new X();//new 出对象,vptr=0 虚函数无法调用
px0->func(); //OK

//px0->virtual_func();//fail 读取位置 0x00000000 时发生访问冲突
delete px0;//调用px0->~X()异常; 读取位置 0x00000000 时发生访问冲突
/*
X construct
X virtual func
X copy construct
100 200 300
*/
  • 虚函数表指针为空

  • 只有虚函数,没有继承。虚函数和普通函数无区别

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    int i = 9;
    printf("&i= %p \n",&i);
    X x1;
    printf("x::func =%p\n",&X::func);

    /*
    long *pvptr=(long*) &x1;
    long* vptr = (long*) (*pvptr);
    printf("virtual_func =%p\n",vptr[1]);//按其定义顺序,第一个为析构函数
    // */
    x1.func();
    x1.virtual_func();
    // 推断func 和virtual_func 函数地址在编译时就已经确定了
    x1.func();
    00782989 lea ecx,[x1]
    0078298C call X::func (07814A1h)
    x1.virtual_func();
    00782991 lea ecx,[x1]
    00782994 call X::virtual_func (07813CAh)
    • 静态联编,编译时就可以确定调用哪个函数,知道函数地址,把调用语句和被调用函数绑定在一起
    • 动态联编,程序运行时根据实际情况,动态把调用语句和被调用函数绑定,一般只在多态和虚函数情况下存在。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    	X* pX0 = new X();
    pX0->func();
    pX0->virtual_func();//__vfptr = 0x00000000 {???, ???}
    //通过虚函数表指针,找到虚函数表,找到virtual_func中函数地址

    pX0->func();
    00BE29EC mov ecx,dword ptr [pX0]
    00BE29EF call X::func (0BE14A1h)
    pX0->virtual_func();
    00BE29F4 mov eax,dword ptr [pX0]
    00BE29F7 mov edx,dword ptr [eax]
    00BE29F9 mov esi,esp
    00BE29FB mov ecx,dword ptr [pX0]
    00BE29FE mov eax,dword ptr [edx+4]
    00BE2A01 call eax
    00BE2A03 cmp esi,esp
    00BE2A05 call __RTC_CheckEsp (0BE11A9h)

18 数据成员绑定时机

1
2
3
4
5
6
7
8
9
10
11

string myvar;
class A {
private:
int myvar;
public:
int myfunc()
{
return myvar;
}
};
  • 编译器对成员函数myfunc的解析是在A类定义完毕后才开始的,定义完毕后,才可以看到A中myvar
  • 对myvar的解析和绑定是在这个类定义完毕后开始的
  • 成员函数中的myvar解析为成员,全局函数中的myvar解析成全局myvar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
string myvar;
class A {

private:
int myvar;
public:
int myfunc();
/*{
return myvar;
}*/
};
int A::myfunc()
{
cout << ::myvar<< endl;//全局
cout << myvar << endl;
return myvar;// 仍然是局部的
}

int myfunc()
{
return myvar; //全局 报错
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef string mytype;
string myvar="global str";
class A {

private:
//typedef int mytype;
mytype m_value;
int myvar;
public:
//void myfunc(mytype val)//string 声明顺序
//{
//
//}
void myfunc(mytype val);
private:
typedef int mytype;
};

//void A::myfunc(mytype val)//报错
//{
//
//}
  • 对于成员函数参数,是在编译器第一次遇到mytype时确定的,mytype第一次遇到时,只看到typedef string mytype;为了在类中尽早看到类型定义语句typedef,挪到类的开头

19 进程内存空间布局

  • 不同数据在内存中有不同的保存时机,保存位置
  • 当运行一个可执行文件时,操作系统将可执行文件加载到内存,此时进程有一个虚拟的地址空间

20181009105508560

20190517215414850

  • 正文段 即代码段
  • BSS段,未被初始化的数据段,不带初值的变量,或者初始化为0的全局量,每次运行不变
  • 初始化的数据段,初始化的变量,全局量,每次运行不变
  • 函数中变量堆栈中,每次运行变化
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
int *ptest = new int(120);
int g1;
int g2;
int g3 = 12;
int g4 = 32;
int g5;
int g6 = 0;
static int g7;
static int g8 = 0;
static int g9 = 10;
void myfunc()
{
return;
}
class MYACLS {
public:
int m_i;
static int m_si;//声明
int m_j;
static int m_sj;
int m_k;
static int m_sk;
};
int MYACLS::m_sj = 0;//定义
printf("ptest= %p \n", &ptest);
printf("&g1= %p \n",&g1);
printf("&g2= %p \n", &g2);
printf("&g3= %p \n", &g3);
printf("&g4= %p \n", &g4);
printf("&g5= %p \n", &g5);
printf("&g6= %p \n", &g6);
printf("&g7= %p \n", &g7);
printf("&g8= %p \n", &g8);
printf("&g9= %p \n", &g9);
printf("&MYACLS::m_sj = %p \n", &(MYACLS::m_sj));

printf("&myfunc = %p \n", myfunc);
printf("&main = %p \n", main);

cout <<"&myfunc = "<<(void*)myfunc << endl;//cout 打印函数指针
  • linux 下 nm命令列出可执行文件中全局变量存放的地址
  • nm 4_2
1
2
3
/*
linux 下 nm
*/
  • 全局地址 不变,

20 数据成员布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MYACLS {
public:
int m_i;
static int m_si;//静态成员变量在类中仅仅是声明
int m_j;
static int m_sj;
int m_k;
static int m_sk;
};

MYACLS myobj;//myobj = {m_i=2 m_j=5 m_k=8 }
cout << sizeof(myobj)<< endl;//12 bytes
myobj.m_i = 2;
myobj.m_j = 5;
myobj.m_k = 8;
  • 普通变量的存储顺序是在类中的定义顺序决定的
1
0x007AFE90  02 00 00 00 05 00 00 00 08 00 00 00 cc cc cc cc b4 fe 7a 00 9e 1f  ............??????z.?.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	MYACLS myobj;//myobj = {m_i=2 m_j=5 m_k=8 }
cout << sizeof(myobj)<< endl;
myobj.m_i = 2;
myobj.m_j = 5;
myobj.m_k = 8;

printf("&myobj.m_i= %p \n",&myobj.m_i);//栈中分配
printf("&myobj.m_j= %p \n", &myobj.m_j);
printf("&myobj.m_k= %p \n", &myobj.m_k);

MYACLS *pmyobj = new MYACLS();//堆中分配
printf("&pmyobj->m_i= %p \n",&pmyobj->m_i);
printf("&pmyobj->m_j= %p \n", &pmyobj->m_j);
printf("&pmyobj->m_k= %p \n", &pmyobj->m_k);

/*
&myobj.m_i= 0115F98C
&myobj.m_j= 0115F990
&myobj.m_k= 0115F994
&pmyobj->m_i= 0141FD50
&pmyobj->m_j= 0141FD54
&pmyobj->m_k= 0141FD58
*/
  • 晚出现变量有较高地址,和Public,private数量无关

  • 边界调整,字节对齐

    • 某些因素导致成员变量之间排列不连续,目的是提高效率,编译器自动调整

    • 在成员之间填补一些字节,使用sizeof凑成一个4/8的整数倍

    • 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
      class MYACLS {
      public:
      int m_i;
      static int m_si;//静态成员变量在类中仅仅是声明
      int m_j;
      static int m_sj;
      int m_k;
      static int m_sk;
      char m_c;
      int m_n;
      };
      MYACLS myobj;//myobj = {m_i=2 m_j=5 m_k=8 }
      cout << sizeof(myobj)<< endl;
      myobj.m_i = 2;
      myobj.m_j = 5;
      myobj.m_k = 8;

      printf("&myobj.m_i= %p \n",&myobj.m_i);
      printf("&myobj.m_j= %p \n", &myobj.m_j);
      printf("&myobj.m_k= %p \n", &myobj.m_k);
      printf("&myobj.m_c= %p \n", &myobj.m_c);
      printf("&myobj.m_n= %p \n", &myobj.m_n);

      MYACLS *pmyobj = new MYACLS();
      printf("&pmyobj->m_i= %p \n",&pmyobj->m_i);
      printf("&pmyobj->m_j= %p \n", &pmyobj->m_j);
      printf("&pmyobj->m_k= %p \n", &pmyobj->m_k);
      printf("&pmyobj->m_c= %p \n", &pmyobj->m_c);
      printf("&pmyobj->m_n= %p \n", &pmyobj->m_n);
      /*
      &myobj.m_i= 0095FA98
      &myobj.m_j= 0095FA9C
      &myobj.m_k= 0095FAA0
      &myobj.m_c= 0095FAA4
      &myobj.m_n= 0095FAA8
      &pmyobj->m_i= 00A66268
      &pmyobj->m_j= 00A6626C
      &pmyobj->m_k= 00A66270
      &pmyobj->m_c= 00A66274
      &pmyobj->m_n= 00A66278
      */
    • 为统一字节对齐问题,引入一字节对齐概念(不对齐)

    • 1
      2
      #pragma pack(1)//1 字节对齐
      #prarma pack()// 取消自定义对齐方式,恢复默认
    • 1个地址 对应一个字节??????????!!!!!!!!!,四个字节,地址相差4???

    • 虚函数,添加vptr虚函数表指针(内部数据成员),虚函数表指针位于开头或末尾

  • 成员变量偏移值,离对象开头首地址偏移多少

    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
    class MYACLS {
    public:
    int m_i;
    static int m_si;//静态成员变量在类中仅仅是声明
    int m_j;
    static int m_sj;
    int m_k;
    static int m_sk;
    char m_c;//1个字节
    int m_n;//4个字节
    private:
    int m_pria;
    int m_prib;

    public:
    void func()
    {
    cout << "-----the offset of data member----"<< endl;//偏移量和类对象关系不大
    printf("&MYACLS.m_i= %p \n", &MYACLS::m_i);//偏移量
    printf("&MYACLS.m_j= %p \n", &MYACLS::m_j);
    printf("&MYACLS.m_k= %p \n", &MYACLS::m_k);

    printf("&MYACLS.m_c= %p \n", &MYACLS::m_c);
    printf("&MYACLS.m_n= %p \n", &MYACLS::m_n);

    printf("&MYACLS.m_pria= %p \n", &MYACLS::m_pria);
    printf("&MYACLS.m_prib= %p \n", &MYACLS::m_prib);
    cout << "---------------------------------" << endl;
    }
    };
    /*
    -----the offset of data member----
    &MYACLS.m_i= 00000000
    &MYACLS.m_j= 00000004
    &MYACLS.m_k= 00000008
    &MYACLS.m_c= 0000000C
    &MYACLS.m_n= 00000010
    &MYACLS.m_pria= 00000014
    &MYACLS.m_prib= 00000018
    ---------------------------------
    */
    1
    2
    3
    //另一种打印偏移量
    #define GET(A,m) (int)(&((A*)0)->m)
    cout << GET(MYACLS,m_prib) << endl;
    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
    57
    58
    59
    60
    61
    62
    // 加上虚函数后
    class MYACLS {
    public:
    int m_i;
    static int m_si;//静态成员变量在类中仅仅是声明
    int m_j;
    static int m_sj;
    int m_k;
    static int m_sk;
    char m_c;//1个字节
    int m_n;//4个字节
    private:
    int m_pria;
    int m_prib;

    public:
    void func()
    {
    cout << "-----the offset of data member----" << endl;
    printf("&MYACLS.m_i= %p \n", &MYACLS::m_i);
    printf("&MYACLS.m_j= %p \n", &MYACLS::m_j);
    printf("&MYACLS.m_k= %p \n", &MYACLS::m_k);

    printf("&MYACLS.m_c= %p \n", &MYACLS::m_c);
    printf("&MYACLS.m_n= %p \n", &MYACLS::m_n);

    printf("&MYACLS.m_pria= %p \n", &MYACLS::m_pria);
    printf("&MYACLS.m_prib= %p \n", &MYACLS::m_prib);
    cout << "---------------------------------" << endl;

    cout << GET(MYACLS, m_prib) << endl;
    }
    ///*
    virtual void myfv()
    {
    }
    //*/
    };

    /*
    32
    &myobj.m_i= 005EFB9C
    &myobj.m_j= 005EFBA0
    &myobj.m_k= 005EFBA4
    &myobj.m_c= 005EFBA8
    &myobj.m_n= 005EFBAC
    &pmyobj->m_i= 008160D4
    &pmyobj->m_j= 008160D8
    &pmyobj->m_k= 008160DC
    &pmyobj->m_c= 008160E0
    &pmyobj->m_n= 008160E4
    -----the offset of data member----
    &MYACLS.m_i= 00000004
    &MYACLS.m_j= 00000008
    &MYACLS.m_k= 0000000C
    &MYACLS.m_c= 00000010
    &MYACLS.m_n= 00000014
    &MYACLS.m_pria= 00000018
    &MYACLS.m_prib= 0000001C
    ---------------------------------
    28
    */
  • 1
    2
    3
    //另一种打印成员偏移
    int MYACLS::*mypoint = &MYACLS::m_n;//定义成员变量指针
    printf("&pmyobj->m_n= %p \n", mypoint);

21 数据成员存取

  • 静态成员变量,可以当作全局变量,只在类的空间内可见,MYACLS::m_si
  • 静态成员变量只有一个实体,保存在可执行文件数据段
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 MYACLS {
public:
int m_i;
static int m_si;
int m_j;
};
int MYACLS::m_si = 10;
/*
MYACLS myobj;
MYACLS *pmobj = new MYACLS();
cout << myobj.m_si<< endl;
cout << MYACLS::m_si << endl;
cout << pmobj->m_si << endl;

printf("myobj.m_i = %p\n",&myobj.m_i);
printf("pmobj->m_i = %p\n", &pmobj->m_i);

printf("MYACLS::m_si = %p\n", &MYACLS::m_si);
printf("myobj.m_si = %p\n", &myobj.m_si);
printf("pmobj->m_si = %p\n", &pmobj->m_si);
*/

/*
10
10
10
myobj.m_i = 0110FBE4
pmobj->m_i = 014B04E8
MYACLS::m_si = 0100A000
myobj.m_si = 0100A000
pmobj->m_si = 0100A000


10
10
10
myobj.m_i = 00EFFE44 //堆栈中每次都发生变化
pmobj->m_i = 0108F860
MYACLS::m_si = 0100A000//数据段中每次地址都一样
myobj.m_si = 0100A000
pmobj->m_si = 0100A000
*/
  • 非静态成员变量,存放在类的对象中,存取通过类对象(类对象指针)

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      /*成员函数
      void func()
      {
      m_i = 5;
      m_j = 6;
      }
      */
      pmyobj->func();
      /*
      MYACLS::func(pmyobj)
      void MYACLS::func(MYACLS* const this)
      {
      this.m_i=5;
      this.m_j=6;
      }
      */
    • 对于普通成员变量访问,编译器将类对象的首地址+成员变量的偏移值访问

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      //&myobj+sizeof(m_i)=&m_j

      /*
      m_i = 5;
      00D51983 mov eax,dword ptr [this]
      00D51986 mov dword ptr [eax],5
      m_j = 6;
      00D5198C mov eax,dword ptr [this]
      00D5198F mov dword ptr [eax+4],6
      */
    • 存在父类时

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      class FAC {
      public:
      int m_fai;
      int m_faj;
      };
      class MYACLS :public FAC{
      public:
      int m_i;
      static int m_si;
      int m_j;
      void func()
      {
      m_i = 5;
      m_j = 6;
      }
      };
      printf("MYACLS::m_i = %p\n", &MYACLS::m_i);//0 变为了 8
  • 虚基类中成员变量访问间接

  • 通过对象访问成员,通过对象指针访问

    22 单一继承下的数据成员布局

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 FAC {
public:
int m_fai;
int m_faj;
};
class MYACLS :public FAC {
public:
int m_i;
int m_j;
};

printf("FAC::m_fai = %p \n",&FAC::m_fai);
printf("FAC::m_faj = %p \n", &FAC::m_faj);

printf("MYACLS::m_fai = %p \n", &MYACLS::m_fai);
printf("MYACLS::m_faj = %p \n", &MYACLS::m_faj);

printf("MYACLS::m_fai = %p \n", &MYACLS::m_i);
printf("MYACLS::m_faj = %p \n", &MYACLS::m_j);

/*
FAC::m_fai = 00000000
FAC::m_faj = 00000004
MYACLS::m_fai = 00000000
MYACLS::m_faj = 00000004
MYACLS::m_fai = 00000008
MYACLS::m_faj = 0000000C
*/
  • 一个子类对象所包含的内容是自己成员+父亲成员的总和

  • 偏移值来看,父类成员先出现,按定义顺序出现,孩子类后出现

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    class Base {
    public:
    int m_i1;
    char m_c1;
    char m_c2;
    char m_c3;
    };
    cout <<sizeof(Base) << endl;
    printf("Base::m_i1 = %p \n", &Base::m_i1);
    printf("Base::m_c1 = %p \n", &Base::m_c1);
    printf("Base::m_c2 = %p \n", &Base::m_c2);
    printf("Base::m_c3 = %p \n", &Base::m_c3);

    /*
    8
    Base::m_i1 = 00000000
    Base::m_c1 = 00000004
    Base::m_c2 = 00000005
    Base::m_c3 = 00000006
    **********************填充(边界调整)
    */
    • 内存紧凑
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Base {
    public:
    int m_i1;
    char m_c1;
    int m_c2;
    char m_c3;
    };
    /*
    16
    Base::m_i1 = 00000000 4
    Base::m_c1 = 00000004 4
    Base::m_c2 = 00000008 4
    Base::m_c3 = 0000000C 4
    */
  • 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
    class Base1 {
    public:
    int m_i1;
    char m_c1;
    };
    class Base2 :public Base1 {
    public:
    char m_c2;
    };
    class Base3 :public Base2 {
    public:
    char m_c3;
    };
    cout << "sizeof(Base1) " << sizeof(Base1) << endl;
    cout << "sizeof(Base2) " << sizeof(Base2) << endl;
    cout << "sizeof(Base3) "<<sizeof(Base3) << endl;
    printf("Base3::m_i1 = %p \n", &Base3::m_i1);
    printf("Base3::m_c1 = %p \n", &Base3::m_c1);
    printf("Base3::m_c2 = %p \n", &Base3::m_c2);
    printf("Base3::m_c3 = %p \n", &Base3::m_c3);

    /*
    sizeof(Base1) 8
    sizeof(Base2) 12
    sizeof(Base3) 16
    Base3::m_i1 = 00000000
    Base3::m_c1 = 00000004
    Base3::m_c2 = 00000008
    Base3::m_c3 = 0000000C
    */
  • 引入继承关系 增加内存空间

  • Visiual Studio 2015 8 ->12 ->16

    • 1563600149145
  • linux G++ 5.4.0 8->12->12

    • 1563600372578
  • linux和windows 布局不一样,编译器实现不一样

  • 内存拷贝谨慎

    • 1
      2
      3
      Base2 b2;
      Base3 b3;
      //不能用Memcpy内存拷贝把Base2中内容拷贝到Base3中

23 单类单继承虚函数下的数据成员布局

  • 单个类带虚函数的数据成员布局

    • 类中引入虚函数时,有额外成本

      • 编译时,编译器产生虚函数表
      • 对象中一个成员变量,虚函数表指针vptr,用于指向虚函数表
      • 增加或者扩展构造函数,增加给虚函数表指针vptr赋值的代码,让vptr指向虚函数表
      • 多重继承,2个父类,每个父类有vptr,子类继承把这两个vptr都继承,有两个vptr,如果子类还有自己额外的虚函数,子类与第一个父类共有一个vptr
      • 析构函数也被扩展虚函数表指针vptr的赋值代码,似乎和构造函数代码相同?????
    • 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 MYACLS {
      public:
      int m_i;
      int m_j;
      virtual void myvirtualfunc()
      {
      }
      MYACLS()
      {
      int abc = 1;
      }
      ~MYACLS()
      {
      int def = 0;
      }
      };

      cout << sizeof(MYACLS)<< endl;
      printf("&MYACLS::m_i %p \n",&MYACLS::m_i);
      printf("&MYACLS::m_j %p \n", &MYACLS::m_j);

      MYACLS myobj;//+ &myobj 0x008ffe30

      myobj.m_i = 3;
      myobj.m_j = 6;

      /*
      12
      &MYACLS::m_i 00000004
      &MYACLS::m_j 00000008
      0x008FFE30 34 8b c2 00 03 00 00 00 06 00 00 00

      */
  • 单一继承父类带虚函数的数据成员布局

    • 1
       

      class Base {
      public:

      int m_bi;
      virtual void mybvirtualfunc()
      {}

      };
      class MYACLS:public Base {
      public:

      int m_i;
      int m_j;
      virtual void myvirtualfunc()
      {
      }
      MYACLS()
      {
          int abc = 1;
      }
      ~MYACLS()
      {
          int def = 0;
      }

      };

      cout << sizeof(MYACLS)<< endl;
      printf("&MYACLS::m_bi %p \n", &MYACLS::m_bi);
      printf("&MYACLS::m_i %p \n",&MYACLS::m_i);
      printf("&MYACLS::m_j %p \n", &MYACLS::m_j);
      
      MYACLS myobj;//+        &myobj    0x008ffe30  //+        &myobj    0x0093f970 
    myobj.m_i = 3;
    myobj.m_j = 6;

/*
16
&MYACLS::m_bi 00000004
&MYACLS::m_i 00000008
&MYACLS::m_j 0000000C
0x0093F970 40 8b 9c 00 cc cc cc cc 03 00 00 00 06 00 00 00
*/
 
1
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

* 单一继承父类不带虚函数的数据成员布局
* ```c++
class Base {
public:
int m_bi;
// virtual void mybvirtualfunc()
// {}
};
class MYACLS:public Base {
public:
int m_i;
int m_j;
virtual void myvirtualfunc()
{
}
MYACLS()
{
int abc = 1;
}
~MYACLS()
{
int def = 0;
}
};

/*
16
&MYACLS::m_bi 00000000 //base类最前面 vptr不在最前在第二个?? error 偏移值不对
&MYACLS::m_i 00000008
&MYACLS::m_j 0000000C

实际内存
0x00EFFBD4 34 8b 4f 00 cc cc cc cc 03 00 00 00 06 00 00 00
*/

1564636968738

24 多重继承数据布局与this调整

  • 单一继承数据成员布局this指针偏移

    • Base类无虚函数,访问Base类成员时,Base 的this指针需要根据子类的this调整

    • 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
      class Base {
      public:
      int m_bi;
      // virtual void mybvirtualfunc()
      // {}
      Base()
      {
      printf("Base this= %p \n",this);
      }
      };
      class MYACLS :public Base {
      public:
      int m_i;
      int m_j;
      virtual void myvirtualfunc()
      {
      }
      MYACLS()
      {
      int abc = 1;
      printf("MYACLS this= %p \n", this);
      }
      ~MYACLS()
      {
      int def = 0;
      }
      };
      cout << sizeof(MYACLS) << endl;
      printf("&MYACLS::m_bi %p \n", &MYACLS::m_bi);
      printf("&MYACLS::m_i %p \n", &MYACLS::m_i);
      printf("&MYACLS::m_j %p \n", &MYACLS::m_j);

      MYACLS myobj;//+ &myobj 0x008ffe30 //+ &myobj 0x0093f970


      myobj.m_i = 3;
      myobj.m_j = 6;
      /*
      16
      &MYACLS::m_bi 00000000
      &MYACLS::m_i 00000008
      &MYACLS::m_j 0000000C
      Base this= 0075F8E0 访问Base成员变量 需要 MYACLS this指针+4
      MYACLS this= 0075F8DC
      */
    • Base有虚函数,子类和父类共享VPTR,this指针相同

    • 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
      class Base {
      public:
      int m_bi;
      virtual void mybvirtualfunc()
      {}
      Base()
      {
      printf("Base this= %p \n",this);
      }
      };
      class MYACLS :public Base {
      public:
      int m_i;
      int m_j;
      virtual void myvirtualfunc()
      {
      }
      MYACLS()
      {
      int abc = 1;
      printf("MYACLS this= %p \n", this);
      }
      ~MYACLS()
      {
      int def = 0;
      }
      };
      cout << sizeof(MYACLS) << endl;
      printf("&MYACLS::m_bi %p \n", &MYACLS::m_bi);
      printf("&MYACLS::m_i %p \n", &MYACLS::m_i);
      printf("&MYACLS::m_j %p \n", &MYACLS::m_j);

      MYACLS myobj;//+ &myobj 0x008ffe30 //+ &myobj 0x0093f970


      myobj.m_i = 3;
      myobj.m_j = 6;
      /*
      16
      &MYACLS::m_bi 00000004
      &MYACLS::m_i 00000008
      &MYACLS::m_j 0000000C
      Base this= 008FFB1C
      MYACLS this= 008FFB1C
      */
    • 1564639647231

  • 多重继承且父类都带虚函数的数据成员布局

    • 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
      57
      58
      59
      60
      61
      62
      63
      64
      class Base {
      public:
      int m_bi;
      virtual void mybvirtualfunc()
      {}
      Base()
      {
      printf("Base this= %p \n",this);
      }
      };
      class Base2 {
      public:
      int m_b2i;
      virtual void mybvirtualfunc2()
      {}
      Base2()
      {
      printf("Base2 this= %p \n", this);
      }
      };
      class MYACLS :public Base,public Base2 {
      public:
      int m_i;
      int m_j;
      virtual void myvirtualfunc()
      {
      }
      MYACLS()
      {
      int abc = 1;
      printf("MYACLS this= %p \n", this);
      }
      ~MYACLS()
      {
      int def = 0;
      }
      };
      cout << sizeof(MYACLS) << endl;
      printf("&MYACLS::m_bi %d \n", &MYACLS::m_bi);
      printf("&MYACLS::m_b2i %d \n", &MYACLS::m_b2i);
      printf("&MYACLS::m_i %d \n", &MYACLS::m_i);
      printf("&MYACLS::m_j %d \n", &MYACLS::m_j);

      MYACLS myobj;//+ &myobj 0x008ffe30 //+ &myobj 0x0093f970


      myobj.m_i = 3;
      myobj.m_j = 6;
      myobj.m_bi = 1;
      myobj.m_b2i = 2;
      /*
      24
      &MYACLS::m_bi 4
      &MYACLS::m_b2i 4
      &MYACLS::m_i 16
      &MYACLS::m_j 20
      Base this= 0086F9C8
      Base2 this= 0086F9D0 //相对于Base this 偏移0xD0-0XC8=8个
      MYACLS this= 0086F9C8

      0x0098F944 84 7b 42 00 01 00 00 00 9c 7b 42 00 02 00 00 00 03 00 00 00 06 ?{B.....?{B..........
      0x0098F959 00 00 00

      */
    • 1564639561643

    • 偏移量指成员变量相对于该原生对象中的偏移

    • 访问Base1中对象不需要偏移,Base2中成员需要偏移8字节+该成员的偏移

    • 访问成员变量—–this指针+成员偏移值

    • 多态。。。。。!!!!!!

    • this指针调整,导致pbase2调整8个字节,=&myobj+8

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      MYACLS myobj;
      //+ &myobj 0x0077f9a8 {m_i=3 m_j=6 } MYACLS *
      //pbase1 = 0x0077f9a8 {m_i=3 m_j=6 }
      //pbase2 = 0x0077f9b0 {m_i=3 m_j=6 }
      Base * pbase1 = &myobj;
      Base2 *pbase2 = &myobj;

      /*
      编译器
      Base2 * pbase2=(Base2*) ( (char*)&myobj +sizeof(Base1) )
      */
    • 1
      2
      3
      4
      5
      6
      //psubobj = 0x00ef0578 {m_i=-842150451 m_j=-842150451 }
      //pbase22 = 0x00ef0580 {m_i=-842150451 m_j=-842150451 } 相差8个字节
      Base2 *pbase22 = new MYACLS();// new 出来24字节
      MYACLS *psubobj =(MYACLS *) pbase22;

      delete pbase22;//会报错,pbase2中保存的地址是分配后的首地址+偏移量

25 虚基类问题的提出

  • 虚基类(虚继承/虚派生)问题提出

    • 存在于三重继承结构中,空间、效率、二义性

    • 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 Grand {
      public:
      int m_grand;
      };

      class A1 :public Grand {
      public:

      };
      class A2 :public Grand {
      public:

      };
      class C1 :public A1,public A2 {
      public:

      };


      cout <<"sizeof(Grand) "<<sizeof(Grand) << endl;
      cout <<"sizeof(A1) "<< sizeof(A1) << endl;
      cout <<"sizeof(A2) "<< sizeof(A2) << endl;
      cout << "sizeof(C1) "<<sizeof(C1) << endl;//8 两份grand

      C1 c1;
      c1.m_grand = 12;//error 访问不明确

      c1.A1::m_grand = 12;
      c1.A2::m_grand = 13;

      /*
      0x007EF778 0c 00 00 00 0d 00 00 00
      */

      grand 只继承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
      34
      35
      class Grand {
      public:
      int m_grand;
      };

      class A1 :public virtual Grand {
      public:

      };
      class A2 :public virtual Grand {
      public:

      };
      class C1 :public A1,public A2 {
      public:

      };

      cout <<"sizeof(Grand) "<<sizeof(Grand) << endl;
      cout <<"sizeof(A1) "<< sizeof(A1) << endl;
      cout <<"sizeof(A2) "<< sizeof(A2) << endl;
      cout << "sizeof(C1) "<<sizeof(C1) << endl;//8 两份grand

      C1 c1;
      c1.m_grand = 12;
      c1.A1::m_grand = 12;
      c1.A2::m_grand = 13;
      /*
      sizeof(Grand) 4
      sizeof(A1) 8
      sizeof(A2) 8
      sizeof(C1) 12

      0x0135FE64 54 8b ea 00 64 8b ea 00 0d 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
      class Grand {
      public:
      // int m_grand;
      };

      class A1 :public virtual Grand {
      public:

      };
      class A2 :public virtual Grand {
      public:

      };
      class C1 :public A1,public A2 {
      public:

      };

      /*
      sizeof(Grand) 1
      sizeof(A1) 4
      sizeof(A2) 4
      sizeof(C1) 8

      */
    • 虚基类表指针 vbptr (virtual base table pointer)指向虚基类表 vbtable (virtual base table)

    • virtual继承后,A1 A2被编译器插入虚基类表指针vbptr,类似成员变量,占用4字节

    • VS开发人员命令行

      • 1
        cl /d1 reportSingleClassLayoutGrand project100.cpp//Grand 类名 无空格
      • 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
        class A1        size(4):
        +---
        0 | {vbptr}
        +---
        +--- (virtual base Grand)
        +---

        A1::$vbtable@:
        0 | 0
        1 | 4 (A1d(A1+0)Grand)
        vbi: class offset o.vbptr o.vbte fVtorDisp
        Grand 4 0 4 0

        class C1 size(8):
        +---
        0 | +--- (base class A1)
        0 | | {vbptr}
        | +---
        4 | +--- (base class A2)
        4 | | {vbptr}
        | +---
        +---
        +--- (virtual base Grand)
        +---

        C1::$vbtable@A1@:
        0 | 0
        1 | 8 (C1d(A1+0)Grand)

        C1::$vbtable@A2@:
        0 | 0
        1 | 4 (C1d(A2+0)Grand)
        vbi: class offset o.vbptr o.vbte fVtorDisp
        Grand 8 0 4 0
      • 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
        class Grand {
        public:
        int m_grand;
        };

        class A1 :public virtual Grand {
        public:
        int m_a1;
        };
        class A2 :public virtual Grand {
        public:
        int m_a2;
        };
        class C1 :public A1,public A2 {
        public:
        int m_c1;
        };

        cout <<"sizeof(Grand) "<<sizeof(Grand) << endl;
        cout <<"sizeof(A1) "<< sizeof(A1) << endl;
        cout <<"sizeof(A2) "<< sizeof(A2) << endl;
        cout << "sizeof(C1) "<<sizeof(C1) << endl;//8 两份grand


        /*

        sizeof(Grand) 4
        sizeof(A1) 12
        sizeof(A2) 12
        sizeof(C1) 24 4*int+2*vbptr

        class C1 size(24):
        +---
        0 | +--- (base class A1)
        0 | | {vbptr}
        4 | | m_a1
        | +---
        8 | +--- (base class A2)
        8 | | {vbptr}
        12 | | m_a2
        | +---
        16 | m_c1
        +---
        +--- (virtual base Grand)
        20 | m_grand
        +---

        */
      • 虚基类始终放在最后面

      • 1564644052354

文章目录
  1. 1. C++对象模型探索
    1. 1.1. 1. 类对象占用的空间
    2. 1.2. 2. 对象结构的发展和演化
      1. 1.2.1. 虚函数
    3. 1.3. 3. This指针调整
    4. 1.4. 4. 分析obj,构造函数语义
    5. 1.5. 7. 拷贝构造函数语义
    6. 1.6. 8. 程序转换语义
    7. 1.7. 9. 程序的优化
      1. 1.7.0.1. 开发者优化
      2. 1.7.0.2. 编译器优化
  2. 1.8. 10. 程序优化续
  3. 1.9. 11 成员初始化列表
  4. 1.10. 12 虚函数表指针位置分析
  5. 1.11. 13 继承关系下的虚函数的手工调用
  6. 1.12. 14 虚函数表分析
  7. 1.13. 15 多重继承的虚函数表分析
  8. 1.14. 16 辅助工具、vpt、vtbl创建时机
  9. 1.15. 17 单纯的类不纯时引发的虚函数调用问题
  10. 1.16. 18 数据成员绑定时机
  11. 1.17. 19 进程内存空间布局
  12. 1.18. 20 数据成员布局
  13. 1.19. 21 数据成员存取
  14. 1.20. 22 单一继承下的数据成员布局
  15. 1.21. 23 单类单继承虚函数下的数据成员布局
  16. 1.22. 24 多重继承数据布局与this调整
  17. 1.23. 25 虚基类问题的提出