首先重新回顾一下关于类/对象大小的计算原则:

类大小计算遵循结构体对齐原则

第一个数据成员放在offset为0的位置

其它成员对齐至min(sizeof(member),#pragma pack(n)所指定的值)的整数倍。

整个结构体也要对齐,结构体总大小对齐至各个min中最大值的整数倍。

win32 可选的有1, 2, 4, 8, 16
linux 32 可选的有1, 2, 4

类的大小与数据成员有关与成员函数无关
类的大小与静态数据成员无关
虚继承对类的大小的影响
虚函数对类的大小的影响

下面通过实例来展示虚继承和虚函数对类大小造成的影响。

测试环境为:Win32 + Vs2008

一、只出现虚继承的情况

 C++ Code 
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
65
66
67
 
#include <iostream>
using namespace std;

class BB
{
public :
      int bb_ ;
};

class B1 : virtual public BB
{
public :
      int b1_ ;
};

class B2 : virtual public BB
{
public :
      int b2_ ;
};

class DD : public B1, public B2
{
public :
      int dd_ ;
};

int main (void)
{
      cout<<sizeof (BB)<< endl;
      cout<<sizeof (B1)<< endl;
      cout<<sizeof (DD)<< endl;

B1 b1 ;
      int** p ;

cout<<&b1 <<endl;
      cout<<&b1 .bb_<< endl;
      cout<<&b1 .b1_<< endl;

p = (int **)&b1;
      cout<<p [0][0]<<endl;
      cout<<p [0][1]<<endl;

DD dd ;
      cout<<&dd <<endl;
      cout<<&dd .bb_<< endl;
      cout<<&dd .b1_<< endl;
      cout<<&dd .b2_<< endl;
      cout<<&dd .dd_<< endl;
      p = (int **)&dd;
      cout<<p [0][0]<<endl;
      cout<<p [0][1]<<endl;
      cout<<endl ;
      cout<<p [2][0]<<endl;
      cout<<p [2][1]<<endl;

BB* pp ;

pp = &dd ;
      dd.bb_ = 10; //对象的内存模型在编译时就已经确定了,否则无法定义类的对象,因为要开辟内存
      int base = pp-> bb_;     // 通过间接访问 (其实pp 已经偏移了20 ),这需要运行时的支持
      cout<<"dd.bb_=" <<base<< endl;

return 0;
}

从输出的地址和虚基类表成员数据可以画出对象内存模型图:

virtual base table

本类地址与虚基类表指针地址的差

虚基类地址与虚基类表指针地址的差

virtual base table pointer(vbptr)

从程序可以看出pp是BB* 指针,通过打印pp 的值与&dd 比较可知,

cout<<(void*)&dd<<endl;
cout<<(void*)pp<<endl;

pp实际上已经偏移了20个字节,如何实现的呢?先找到首个vbptr,找到虚基类BB地址与虚基类表指针地址的差,也即是20,接着pp偏移20个字节指向了dd对象中的BB部分,然后就访问到了bb_,这是在运行时才做的转换。记住:C++标准规定对对象取地址将始终为对应类型的首地址。

二、只出现虚函数的情况

(一):一般继承

 C++ Code 
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
 
#include <iostream>
using namespace std;

class Base
{
public :
    virtual void Fun1()
    {
        cout << "Base::Fun1 ..." << endl;
    }

virtual void Fun2()
    {
        cout << "Base::Fun2 ..." << endl;
    }
    int data1_ ;
};

class Derived : public Base
{
public :
    void Fun2 ()
    {
        cout << "Derived::Fun2 ..." << endl;
    }
    virtual void Fun3()
    {
        cout << "Derived::Fun3 ..." << endl;
    }
    int data2_ ;
};

typedef void (* FUNC)(void );

int main (void)
{
    cout << sizeof (Base) << endl;
    cout << sizeof (Derived) << endl;
    Base b ;
    int **p = (int **)& b;
    FUNC fun = (FUNC) p[0][0];
    fun();
    fun = (FUNC )p[0][1];
    fun();
    cout << endl ;

Derived d ;
    p = (int **)&d;
    fun = (FUNC )p[0][0];
    fun();
    fun = (FUNC )p[0][1];
    fun();
    fun = (FUNC )p[0][2];
    fun();

return 0;
}

从输出的函数体可以画出对象内存模型图:

vtbl:虚函数表(存放虚函数的函数指针)

vptr:虚函数表指针

从输出可以看出,Derived类继承了Base::Fun1,而覆盖了Fun2,此外还有自己的Fun3。注意,因为Fun3是虚函数,才会出现在虚函数表,如果是一般函数是不会的,因为不用通过vptr间接访问。

(二)、钻石继承

 C++ Code 
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
 
#include <iostream>
using namespace std;

class BB
{
public:
    virtual void vpbb()
    {
        cout << "BB:vpbb().." << endl;
    }
    int bb_;
};
class B1 : public BB
{
public:
    virtual void vpb1()
    {
        cout << "B1:vpb1().." << endl;
    }
    int b1_;
};
class B2 : public BB
{
public:
    virtual void vpb2()
    {
        cout << "B2:vpb2().." << endl;
    }
    int b2_;
};
class DD : public B1, public B2
{
public:
    virtual void vpdd()
    {
        cout << "DD:vpdd().." << endl;
    }
    int dd_;
};

typedef void (* FUNC)(void );

int main()
{
    cout << sizeof(BB) << endl;
    cout << sizeof(B1) << endl;
    cout << sizeof(DD) << endl;
    cout << endl;

DD dd ;
    cout << &dd << endl;
    cout << &dd.B1::bb_ << endl;
    cout << &dd.B2::bb_ << endl;
    cout << &dd .b1_ << endl;
    cout << &dd .b2_ << endl;
    cout << &dd .dd_ << endl;
    cout << endl;

B1 b ;
    int **p = (int **)& b;
    FUNC fun = (FUNC) p[0][0];
    fun();
    fun = (FUNC )p[0][1];
    fun();
    cout << endl ;

p = (int **)&dd
    fun = (FUNC)p[0][0];
    fun();
    fun = (FUNC)p[0][1];
    fun();
    fun = (FUNC)p[0][2];
    fun();

fun = (FUNC)p[3][0];
    fun();
    fun = (FUNC)p[3][1];
    fun();

cout << endl;

return 0;
}

从成员输出的地址和通过虚函数表指针访问到的函数可以画出模型:

DD::vfdd 的位置跟继承的顺序有关,如果DD先继承的是B2, 那么它将跟在B2::vfb2 的下面。

如果派生类是从多个基类继承或者有多个继承分支(从所有根类开始算起),而其中若干个继承分支上出现了多态类,则派生类将从这些分支中的每个分支上继承一个vptr,编译器也将为它生成多个vtable,有几个vptr就生成几个vtable(每个vptr分别指向其中一个),分别与它的多态基类对应。

三、虚继承与虚函数同时出现的情况:

 C++ Code 
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
 
#include <iostream>
using namespace std;

class BB
{
public :
      virtual void vfbb()
     {
           cout<<"BB::vfbb" <<endl;
     }
      virtual void vfbb2()
     {
           cout<<"BB::vfbb2" <<endl;
     }
      int bb_ ;
};

class B1 : virtual public BB
{
public :
      virtual void vfb1()
     {
           cout<<"B1::vfb1" <<endl;
     }
      int b1_ ;
};

class B2 : virtual public BB
{
public :
      virtual void vfb2()
     {
           cout<<"B2::vfb2" <<endl;
     }
      int b2_ ;
};

class DD : public B1, public B2
{
public :
      virtual void vfdd()
     {
           cout<<"DD::vfdd" <<endl;
     }
      int dd_ ;
};

typedef void (* FUNC)(void);

int main (void)
{
      cout<<sizeof (BB)<< endl;
      cout<<sizeof (B1)<< endl;
      cout<<sizeof (DD)<< endl;

BB bb ;
      int** p ;
      p = (int **)&bb;
      FUNC fun ;
      fun = (FUNC )p[0][0];
      fun();
      fun = (FUNC )p[0][1];
      fun();
      cout<<endl ;

B1 b1 ;
     
      p = (int **)&b1;
      fun = (FUNC )p[0][0];
      fun();
      fun = (FUNC )p[3][0];
      fun();
      fun = (FUNC )p[3][1];
      fun();

cout<<p [1][0]<<endl;
      cout<<p [1][1]<<endl;
      cout<<endl ;

DD dd ;
      p = (int **)&dd;
      fun = (FUNC )p[0][0];
      fun();
      fun = (FUNC )p[0][1]; // DD::vfdd 挂在 B1::vfb1的下面
      fun();
      fun = (FUNC )p[3][0];
      fun();
      fun = (FUNC )p[7][0];
      fun();
      fun = (FUNC )p[7][1];
      fun();
     
      cout<<p [1][0]<<endl;
      cout<<p [1][1]<<endl;
      cout<<p [4][0]<<endl;
      cout<<p [4][1]<<endl;

return 0;
}

从输出的虚基类表成员数据和虚函数体可以画出对象内存模型图:

上图中vfdd 出现的位置跟继承的顺序有关,如果DD先继承的是B2,那么它将跟在vfb2 的下面。

注意:如果没有虚继承,则虚函数表会合并,一个类只会存在一个虚函数表和一个虚函数表指针(同个类的对象共享),当然也不会有虚基类表和虚基类表指针的存在。
但如果是钻石继承,那么是会存在两份虚函数表和两份虚函数表指针的。

参考:

《深入探索C++对象模型》

C++ primer 第四版
Effective C++ 3rd
C++编程规范

对C++对象内存模型造成的影响(类/对象的大小)的更多相关文章

  1. 从零开始学C++之虚继承和虚函数对C++对象内存模型造成的影响

    首先重新回顾一下关于类/对象大小的计算原则: 类大小计算遵循结构体对齐原则 第一个数据成员放在offset为0的位置 其它成员对齐至min(sizeof(member),#pragma pack(n) ...

  2. C++/C#中堆栈、对象内存模型、深浅拷贝、Array.Clone方法

    转载自:http://blog.csdn.net/jarvischu/article/details/6425534 目录 1.      C++/C#中对象内存模型................. ...

  3. C#的对象内存模型

    转载自:http://www.cnblogs.com/alana/archive/2012/07/05/2577893.html C#的对象内存模型: 一.栈内存和堆内存1.栈内存 由编译器自动分配和 ...

  4. (转)c#对象内存模型

    对象内存模型 C#的对象内存模型写这篇博客的主要目的是为了加深自己的理解,如有不对的地方,请各位见谅. C#的对象内存模型: 一.栈内存和堆内存1.栈内存 由编译器自动分配和释放,主要用来保存一些局部 ...

  5. Swift 对象内存模型探究(一)

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/zIkB9KnAt1YPWGOOwyqY3Q 作者:王 ...

  6. Objective-C类成员变量深度剖析--oc对象内存模型

    目录 Non Fragile ivars 为什么Non Fragile ivars很关键 如何寻址类成员变量 真正的“如何寻址类成员变量” Non Fragile ivars布局调整 为什么Objec ...

  7. C++对象内存模型2 (虚函数,虚指针,虚函数表)

    从例子入手,考察如下带有虚函数的类的对象内存模型: class A { public: virtual void vfunc1(); virtual void vfunc2(); void func1 ...

  8. C++对象内存模型1(堆栈模型)

    对象内存模型 一. 栈(Stack) VS. 堆(heap) 栈 由系统自动管理,以执行函数为单位 空间大小编译时确定(参数+局部变量) 函数执行时,系统自动分配一个stack 函数执行结束时,系统立 ...

  9. C++对象内存模型1(堆栈模型)(转)

    对象内存模型 一. 栈(Stack) VS. 堆(heap) 栈 由系统自动管理,以执行函数为单位 空间大小编译时确定(参数+局部变量) 函数执行时,系统自动分配一个stack 函数执行结束时,系统立 ...

随机推荐

  1. WICED™ <SMART> Software Development Kit

    WICED™ Software Development Kit The WICED™ SDK includes the tools and software needed to create Wi-F ...

  2. head first---------facade design pattern

    head first----------外观模式或者门面模式         外观模式又名门面模式:提供了一个统一的接口,用来访问子系统中的一群接口.外观模式定义了一个高层接口,从而让子系统更容易使用 ...

  3. MySQL审计功能

    http://blog.itpub.net/29733787/viewspace-1604392/

  4. 怎样教你牢记17个的Win7快捷键!

    常规快捷键在开始使用Win7中神奇的快捷键加速我们的电脑操作之前,先给大家介绍几个从Win2000到现在一直通用的“资源管理器”快捷键,权当作热身吧!Win+E: 打开“资源管理器”.Win+R: 打 ...

  5. USACO numtri 数塔问题

    /* ID:kevin_s1 PROG:numtri LANG:C++ */ #include <iostream> #include <cstdio> #include &l ...

  6. C++入门级 一

    如果您想学习电脑编程,却又不知从何入手,那么您不妨看看下面的几种学习方案,可能会给您一些启示吧! 方案一 Basic语言 & Visual Basic 优点 (1)Basic 简单易学,很容易 ...

  7. OpenSSL再曝CCS注入漏洞-心伤未愈又成筛子

    太戏剧了,昨晚看了佳片有约,还不错,2012版的<完美回顾>,像我这样的人依旧选择用电视或者去影院看电影,在没有中间插播广告的时候,体验憋尿得过程中,总是能突然有非常多的想法,这是用电脑或 ...

  8. iOS: sqlite数据库的基本操作

    介绍: sqlite3(3是版本)是本地系统中的一个小型数据库,因为它没有在数据维护和安全上做过多的操作,所以它存储处理数据时,非常简单方便,但是它是不安全和不可靠的,如果一旦误操作删除了数据,是没有 ...

  9. Hadoop HDFS分布式文件系统设计要点与架构(转摘)

    Hadoop简介:一个分布式系统基础架构,由Apache基金会开发.用户可以在不了解分布式底层细节的情况下,开发分布式程序.充分利用集群的威力高速运算和存储.Hadoop实现了一个分布式文件系统(Ha ...

  10. android获取周围AP信息(下)

    疑问: 在上一篇中,还有一个问题未解决:WifiManager的startscan() 方法是立即返回的,也就是说这个方法会调用一个扫描wifi信号的线程,那么这个扫描什么时候结束呢?我们又该什么时候 ...