C++对象内存模型

C++ 对象的内存布局

陈皓

http://blog.csdn.net/haoel

前言

07年12月,我写了一篇《C++虚函数表解析》的文章,引起了大家的兴趣。有很多朋友对我的文章留了言,有鼓励我的,有批评我的,还有很多问问题的。我在这里一并对大家的留言表示感谢。这也是我为什么再写一篇续言的原因。因为,在上一篇文章中,我用了的示例都是非常简单的,主要是为了说明一些机理上的问题,也是为了图一些表达上方便和简单。不想,这篇文章成为了打开C++对象模型内存布局的一个引子,引发了大家对C++对象的更深层次的讨论。当然,我之前的文章还有很多方面没有涉及,从我个人感觉下来,在谈论虚函数表里,至少有以下这些内容没有涉及:

1)有成员变量的情况。

2)有重复继承的情况。

3)有虚拟继承的情况。

4)有钻石型虚拟继承的情况。

这些都是我本篇文章需要向大家说明的东西。所以,这篇文章将会是《C++虚函数表解析》的一个续篇,也是一篇高级进阶的文章。我希望大家在读这篇文章之前对C++有一定的基础和了解,并能先读我的上一篇文章。因为这篇文章的深度可能会比较深,而且会比较杂乱,我希望你在读本篇文章时不会有大脑思维紊乱导致大脑死机的情况。;-)

对象的影响因素

简而言之,我们一个类可能会有如下的影响因素:

1)成员变量

2)虚函数(产生虚函数表)

3)单一继承(只继承于一个类)

4)多重继承(继承多个类)

5)重复继承(继承的多个父类中其父类有相同的超类)

6)虚拟继承(使用virtual方式继承,为了保证继承后父类的内存布局只会存在一份)

上述的东西通常是C++这门语言在语义方面对对象内部的影响因素,当然,还会有编译器的影响(比如优化),还有字节对齐的影响。在这里我们都不讨论,我们只讨论C++语言上的影响。

本篇文章着重讨论下述几个情况下的C++对象的内存布局情况。

1)单一的一般继承(带成员变量、虚函数、虚函数覆盖)

2)单一的虚拟继承(带成员变量、虚函数、虚函数覆盖)

3)多重继承(带成员变量、虚函数、虚函数覆盖)

4)重复多重继承(带成员变量、虚函数、虚函数覆盖)

5)钻石型的虚拟多重继承(带成员变量、虚函数、虚函数覆盖)

我们的目标就是,让事情越来越复杂。

知识复习

我们简单地复习一下,我们可以通过对象的地址来取得虚函数表的地址,如:

typedef void(*Fun)(void);

Base
b;

Fun pFun =
NULL;

cout <<
"虚函数表地址:" << (int*)(&b) << endl;

cout <<
"虚函数表
— 第一个函数地址:" << (int*)*(int*)(&b) << endl;

// Invoke the first virtual function

pFun =
(Fun)*((int*)*(int*)(&b));

pFun();

我们同样可以用这种方式来取得整个对象实例的内存布局。因为这些东西在内存中都是连续分布的,我们只需要使用适当的地址偏移量,我们就可以获得整个内存对象的布局。

本篇文章中的例程或内存布局主要使用如下编译器和系统:

1)Windows XP 和 VC++ 2003

2)Cygwin 和 G++ 3.4.4

单一的一般继承

下面,我们假设有如下所示的一个继承关系:

请注意,在这个继承关系中,父类,子类,孙子类都有自己的一个成员变量。而了类覆盖了父类的f()方法,孙子类覆盖了子类的g_child()及其超类的f()。

我们的源程序如下所示:

class Parent
{

public:

int
iparent;

Parent ():iparent
(10)
{}

virtual void f() { cout <<
" Parent::f()" << endl;
}

virtual void g() { cout <<
" Parent::g()" << endl;
}

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

};

class Child :
public Parent
{

public:

int
ichild;

Child():ichild(100) {}

virtual void f() { cout <<
"Child::f()"
<< endl; }

virtual void g_child() { cout <<
"Child::g_child()"
<< endl; }

virtual void h_child() { cout <<
"Child::h_child()"
<< endl; }

};

class GrandChild :
public
Child{

public:

int
igrandchild;

GrandChild():igrandchild(1000)
{}

virtual void f() { cout <<
"GrandChild::f()"
<< endl; }

virtual void g_child() { cout <<
"GrandChild::g_child()" << endl; }

virtual void h_grandchild() { cout
<< "GrandChild::h_grandchild()"
<< endl; }

};

我们使用以下程序作为测试程序:(下面程序中,我使用了一个int** pVtab 来作为遍历对象内存布局的指针,这样,我就可以方便地像使用数组一样来遍历所有的成员包括其虚函数表了,在后面的程序中,我也是用这样的方法的,请不必感到奇怪,)

typedef void(*Fun)(void);

GrandChild gc;

int** pVtab = (int**)&gc;

cout <<
"[0] GrandChild::_vptr->" << endl;

for (int i=0; (Fun)pVtab[0][i]!=NULL;
i++){

pFun =
(Fun)pVtab[0][i];

cout << "    ["<<i<<"]
";

pFun();

}

cout <<
"[1] Parent.iparent = " << (int)pVtab[1] << endl;

cout <<
"[2] Child.ichild = " << (int)pVtab[2] << endl;

cout << "[3] GrandChild.igrandchild = "
<< (int)pVtab[3] << endl;

其运行结果如下所示:(在VC++ 2003和G++ 3.4.4下)


[0] GrandChild::_vptr->

    [0]
GrandChild::f()

    [1]
Parent::g()

    [2]
Parent::h()

    [3]
GrandChild::g_child()

    [4]
Child::h1()

    [5]
GrandChild::h_grandchild()

[1] Parent.iparent = 10

[2] Child.ichild = 100

[3]
GrandChild.igrandchild = 1000

使用图片表示如下:

可见以下几个方面:

1)虚函数表在最前面的位置。

2)成员变量根据其继承和声明顺序依次放在后面。

3)在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新。

多重继承

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类只overwrite了父类的f()函数,而还有一个是自己的函数(我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:

我们的类继承的源代码如下所示:父类的成员初始为10,20,30,子类的为100

class Base1
{

public:

int
ibase1;

Base1():ibase1(10) {}

virtual void f() { cout <<
"Base1::f()"
<< endl; }

virtual void g() { cout <<
"Base1::g()"
<< endl; }

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

};

class Base2
{

public:

int
ibase2;

Base2():ibase2(20) {}

virtual void f() { cout <<
"Base2::f()"
<< endl; }

virtual void g() { cout <<
"Base2::g()"
<< endl; }

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

};

class Base3
{

public:

int
ibase3;

Base3():ibase3(30) {}

virtual void f() { cout <<
"Base3::f()"
<< endl; }

virtual void g() { cout <<
"Base3::g()"
<< endl; }

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

};

class Derive :
public Base1,
public Base2,
public Base3
{

public:

int
iderive;

Derive():iderive(100) {}

virtual void f() { cout <<
"Derive::f()"
<< endl; }

virtual void g1() { cout <<
"Derive::g1()"
<< endl; }

};

我们通过下面的程序来查看子类实例的内存布局:下面程序中,注意我使用了一个s变量,其中用到了sizof(Base)来找下一个类的偏移量。(因为我声明的是int成员,所以是4个字节,所以没有对齐问题。关于内存的对齐问题,大家可以自行试验,我在这里就不多说了)

typedef void(*Fun)(void);

Derive
d;

int** pVtab = (int**)&d;

cout << "[0] Base1::_vptr->" <<
endl;

pFun =
(Fun)pVtab[0][0];

cout << "     [0]
";

pFun();

pFun =
(Fun)pVtab[0][1];

cout << "     [1]
";pFun();

pFun =
(Fun)pVtab[0][2];

cout << "     [2]
";pFun();

pFun =
(Fun)pVtab[0][3];

cout << "     [3]
";
pFun();

pFun =
(Fun)pVtab[0][4];

cout
<< "    
[4] ";
cout<<pFun<<endl;

cout << "[1] Base1.ibase1 = " <<
(int)pVtab[1] << endl;

                int s =
sizeof(Base1)/4;

cout << "[" << s <<
"] Base2::_vptr->"<<endl;

pFun =
(Fun)pVtab[s][0];

cout << "     [0]
"; pFun();

Fun =
(Fun)pVtab[s][1];

cout << "     [1]
"; pFun();

pFun =
(Fun)pVtab[s][2];

cout << "     [2]
"; pFun();

pFun =
(Fun)pVtab[s][3];

out << "     [3]
";

cout<<pFun<<endl;

cout << "["<< s+1 <<"] Base2.ibase2 = " <<
(int)pVtab[s+1] << endl;

s = s + sizeof(Base2)/4;

cout << "[" << s <<
"] Base3::_vptr->"<<endl;

pFun =
(Fun)pVtab[s][0];

cout << "     [0]
"; pFun();

pFun =
(Fun)pVtab[s][1];

cout << "     [1]
";
pFun();

pFun =
(Fun)pVtab[s][2];

cout << "     [2]
"; pFun();

pFun =
(Fun)pVtab[s][3];

cout << "     [3]
";

cout<<pFun<<endl;

s++;

cout << "["<< s
<<"] Base3.ibase3 = " << (int)pVtab[s] << endl;

s++;

cout << "["<< s <<"]
Derive.iderive = " << (int)pVtab[s] <<
endl;

其运行结果如下所示:(在VC++ 2003和G++ 3.4.4下)


[0] Base1::_vptr->

     [0]
Derive::f()

     [1]
Base1::g()

     [2]
Base1::h()

     [3]
Driver::g1()

     [4] 00000000      ? 注意:在GCC下,这里是1

[1] Base1.ibase1 = 10

[2] Base2::_vptr->

     [0]
Derive::f()

     [1]
Base2::g()

     [2]
Base2::h()

     [3] 00000000      ? 注意:在GCC下,这里是1

[3] Base2.ibase2 = 20

[4] Base3::_vptr->

     [0]
Derive::f()

     [1]
Base3::g()

     [2]
Base3::h()

     [3]
00000000

[5] Base3.ibase3 = 30

[6]
Derive.iderive = 100

使用图片表示是下面这个样子:

我们可以看到:

1)  每个父类都有自己的虚表。

2)  子类的成员函数被放到了第一个父类的表中。

3)  内存布局中,其父类布局依次按声明顺序排列。

4)  每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

重复继承

下面我们再来看看,发生重复继承的情况。所谓重复继承,也就是某个基类被间接地重复继承了多次。

下图是一个继承图,我们重载了父类的f()函数。

其类继承的源代码如下所示。其中,每个类都有两个变量,一个是整形(4字节),一个是字符(1字节),而且还有自己的虚函数,自己overwrite父类的虚函数。如子类D中,f()覆盖了超类的函数, f1() 和f2() 覆盖了其父类的虚函数,Df()为自己的虚函数。

class
B

{

public:

int
ib;

char
cb;

public:

B():ib(0),cb(‘B‘)
{}

virtual void f() { cout <<
"B::f()" <<
endl;}

virtual void Bf() { cout <<
"B::Bf()" <<
endl;}

};

class B1 :  public
B

{

public:

int
ib1;

char
cb1;

public:

B1():ib1(11),cb1(‘1‘) {}

virtual void f() { cout <<
"B1::f()" <<
endl;}

virtual void f1() { cout <<
"B1::f1()" <<
endl;}

virtual void Bf1() { cout <<
"B1::Bf1()"
<< endl;}

};

class B2:  public
B

{

public:

int
ib2;

char
cb2;

public:

B2():ib2(12),cb2(‘2‘) {}

virtual void f() { cout <<
"B2::f()" <<
endl;}

virtual void f2() { cout <<
"B2::f2()" <<
endl;}

virtual void Bf2() { cout <<
"B2::Bf2()"
<< endl;}

};

class D :
public B1,
public
B2

{

public:

int
id;

char
cd;

public:

D():id(100),cd(‘D‘)
{}

virtual void f() { cout <<
"D::f()" <<
endl;}

virtual void f1() { cout <<
"D::f1()" <<
endl;}

virtual void f2() { cout <<
"D::f2()" <<
endl;}

virtual void Df() { cout <<
"D::Df()" <<
endl;}

};

我们用来存取子类内存布局的代码如下所示:(在VC++ 2003和G++ 3.4.4下)

typedef void(*Fun)(void);

int** pVtab =
NULL;

Fun pFun =
NULL;

D
d;

pVtab = (int**)&d;

cout <<
"[0] D::B1::_vptr->" << endl;

pFun =
(Fun)pVtab[0][0];

cout <<
"    
[0] "; 
  pFun();

pFun =
(Fun)pVtab[0][1];

cout <<
"    
[1] ";   
pFun();

pFun =
(Fun)pVtab[0][2];

cout <<
"    
[2] ";   
pFun();

pFun =
(Fun)pVtab[0][3];

cout <<
"    
[3] ";   
pFun();

pFun =
(Fun)pVtab[0][4];

cout <<
"    
[4] ";   
pFun();

pFun =
(Fun)pVtab[0][5];

cout <<
"    
[5] 0x" << pFun <<
endl;

cout <<
"[1] B::ib = "
<< (int)pVtab[1] << endl;

cout <<
"[2] B::cb = "
<< (char)pVtab[2] << endl;

cout <<
"[3] B1::ib1 = "
<< (int)pVtab[3] << endl;

cout <<
"[4] B1::cb1 = "
<< (char)pVtab[4] << endl;

cout <<
"[5] D::B2::_vptr->" << endl;

pFun =
(Fun)pVtab[5][0];

cout <<
"    
[0] ";   
pFun();

pFun =
(Fun)pVtab[5][1];

cout <<
"    
[1] ";   
pFun();

pFun =
(Fun)pVtab[5][2];

cout <<
"    
[2] ";   
pFun();

pFun =
(Fun)pVtab[5][3];

cout <<
"    
[3] ";   
pFun();

pFun =
(Fun)pVtab[5][4];

cout <<
"    
[4] 0x" << pFun <<
endl;

cout <<
"[6] B::ib = "
<< (int)pVtab[6] << endl;

cout <<
"[7] B::cb = "
<< (char)pVtab[7] << endl;

cout <<
"[8] B2::ib2 = "
<< (int)pVtab[8] << endl;

cout <<
"[9] B2::cb2 = "
<< (char)pVtab[9] << endl;

cout <<
"[10] D::id = "
<< (int)pVtab[10] << endl;

cout << "[11] D::cd = " <<
(char)pVtab[11] << endl;

程序运行结果如下:


GCC
3.4.4


VC++
2003


[0]
D::B1::_vptr->

     [0]
D::f()

     [1]
B::Bf()

     [2]
D::f1()

     [3]
B1::Bf1()

     [4]
D::f2()

     [5]
0x1

[1] B::ib =
0

[2] B::cb =
B

[3] B1::ib1 =
11

[4] B1::cb1 =
1

[5]
D::B2::_vptr->

     [0]
D::f()

     [1]
B::Bf()

     [2]
D::f2()

     [3]
B2::Bf2()

     [4]
0x0

[6] B::ib =
0

[7] B::cb =
B

[8] B2::ib2 =
12

[9] B2::cb2 =
2

[10] D::id =
100

[11] D::cd =
D


[0]
D::B1::_vptr->

     [0]
D::f()

     [1]
B::Bf()

     [2]
D::f1()

     [3]
B1::Bf1()

     [4]
D::Df()

     [5]
0x00000000

[1] B::ib =
0

[2] B::cb =
B

[3] B1::ib1 =
11

[4] B1::cb1 =
1

[5]
D::B2::_vptr->

     [0]
D::f()

     [1]
B::Bf()

     [2]
D::f2()

     [3]
B2::Bf2()

     [4]
0x00000000

[6] B::ib =
0

[7] B::cb =
B

[8] B2::ib2 =
12

[9] B2::cb2 =
2

[10] D::id =
100

[11] D::cd =
D

下面是对于子类实例中的虚函数表的图:

我们可以看见,最顶端的父类B其成员变量存在于B1和B2中,并被D给继承下去了。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两份,一份是B1继承而来的,另一份是B2继承而来的。所以,如果我们使用以下语句,则会产生二义性编译错误:

D d;

d.ib = 0;               //二义性错误

d.B1::ib = 1;          
//正确

d.B2::ib = 2;          
//正确

注意,上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误,但B类在D中还是有两个实例,这种继承造成了数据的重复,我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以,C++引入了虚基类的概念。

钻石型多重虚拟继承

虚拟继承的出现就是为了解决重复继承中多个间接父类的问题的。钻石型的结构是其最经典的结构。也是我们在这里要讨论的结构:

上述的“重复继承”只需要把B1和B2继承B的语法中加上virtual 关键,就成了虚拟继承,其继承图如下所示:

上图和前面的“重复继承”中的类的内部数据和接口都是完全一样的,只是我们采用了虚拟继承:其省略后的源码如下所示:

class B
{……};

class B1 :
virtual public B{……};

class B2:
virtual public B{……};

class D :
public B1,
public B2{
…… };

在查看D之前,我们先看一看单一虚拟继承的情况。下面是一段在VC++2003下的测试程序:(因为VC++和GCC的内存而局上有一些细节上的不同,所以这里只给出VC++的程序,GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试):

int** pVtab =
NULL;

Fun pFun =
NULL;

B1
bb1;

pVtab = (int**)&bb1;

cout <<
"[0] B1::_vptr->" << endl;

pFun =
(Fun)pVtab[0][0];

cout <<
"    
[0] ";

pFun(); //B1::f1();

cout <<
"    
[1] ";

pFun =
(Fun)pVtab[0][1];

pFun(); //B1::bf1();

cout <<
"    
[2] ";

cout <<
pVtab[0][2]
<< endl;

cout <<
"[1] = 0x";

cout <<
(int*)*((int*)(&bb1)+1) <<endl; //B1::ib1

cout <<
"[2] B1::ib1 = ";

cout <<
(int)*((int*)(&bb1)+2) <<endl; //B1::ib1

cout <<
"[3] B1::cb1 = ";

cout <<
(char)*((int*)(&bb1)+3) << endl; //B1::cb1

cout <<
"[4] = 0x";

cout <<
(int*)*((int*)(&bb1)+4) << endl; //NULL

cout <<
"[5] B::_vptr->"
<< endl;

pFun =
(Fun)pVtab[5][0];

cout <<
"    
[0] ";

pFun(); //B1::f();

pFun =
(Fun)pVtab[5][1];

cout <<
"    
[1] ";

pFun(); //B::Bf();

cout <<
"    
[2] ";

cout <<
"0x" <<
(Fun)pVtab[5][2]
<< endl;

cout <<
"[6] B::ib = ";

cout <<
(int)*((int*)(&bb1)+6) <<endl; //B::ib

cout <<
"[7] B::cb = ";

其运行结果如下(我结出了GCC的和VC++2003的对比):


GCC 3.4.4


VC++ 2003


[0] B1::_vptr
->

    [0] : B1::f()

    [1] : B1::f1()

    [2] : B1::Bf1()

    [3] : 0

[1] B1::ib1 :
11

[2] B1::cb1 :
1

[3] B::_vptr
->

    [0] : B1::f()

    [1] : B::Bf()

    [2]
: 0

[4] B::ib :
0

[5] B::cb :
B

[6] NULL :
0


[0]
B1::_vptr->

     [0] B1::f1()

     [1] B1::Bf1()

     [2] 0

[1] =
0x00454310

?该地址取值后是-4

[2] B1::ib1 =
11

[3] B1::cb1 =
1

[4] =
0x00000000

[5]
B::_vptr->

     [0] B1::f()

     [1] B::Bf()

     [2] 0x00000000

[6] B::ib =
0

[7] B::cb =
B

这里,大家可以自己对比一下。关于细节上,我会在后面一并再说。

下面的测试程序是看子类D的内存布局,同样是VC++ 2003的(因为VC++和GCC的内存布局上有一些细节上的不同,而VC++的相对要清楚很多,所以这里只给出VC++的程序,GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试):

D
d;

pVtab = (int**)&d;

cout <<
"[0] D::B1::_vptr->" << endl;

pFun =
(Fun)pVtab[0][0];

cout <<
"    
[0] ";   
pFun(); //D::f1();

pFun =
(Fun)pVtab[0][1];

cout <<
"    
[1] ";   
pFun(); //B1::Bf1();

pFun =
(Fun)pVtab[0][2];

cout <<
"    
[2] ";   
pFun(); //D::Df();

pFun =
(Fun)pVtab[0][3];

cout <<
"    
[3] ";

cout << pFun
<< endl;

//cout << pVtab[4][2] <<
endl;

cout <<
"[1] = 0x";

cout <<  (int*)((&dd)+1) <<endl; //????

cout <<
"[2] B1::ib1 = ";

cout <<
*((int*)(&dd)+2) <<endl; //B1::ib1

cout <<
"[3] B1::cb1 = ";

cout <<
(char)*((int*)(&dd)+3) << endl; //B1::cb1

//---------------------

cout <<
"[4] D::B2::_vptr->" << endl;

pFun =
(Fun)pVtab[4][0];

cout <<
"    
[0] ";   
pFun(); //D::f2();

pFun =
(Fun)pVtab[4][1];

cout <<
"    
[1] ";   
pFun(); //B2::Bf2();

pFun =
(Fun)pVtab[4][2];

cout <<
"    
[2] ";

cout << pFun
<< endl;

cout <<
"[5] = 0x";

cout <<
*((int*)(&dd)+5) << endl; //
???

cout <<
"[6] B2::ib2 = ";

cout <<
(int)*((int*)(&dd)+6) <<endl; //B2::ib2

cout <<
"[7] B2::cb2 = ";

cout <<
(char)*((int*)(&dd)+7) << endl; //B2::cb2

cout <<
"[8] D::id = ";

cout <<
*((int*)(&dd)+8) << endl; //D::id

cout <<
"[9] D::cd = ";

cout <<
(char)*((int*)(&dd)+9) << endl;//D::cd

cout <<
"[10] 
= 0x";

cout <<
(int*)*((int*)(&dd)+10) << endl;

//---------------------

cout <<
"[11] D::B::_vptr->" << endl;

pFun =
(Fun)pVtab[11][0];

cout <<
"    
[0] ";   
pFun(); //D::f();

pFun =
(Fun)pVtab[11][1];

cout <<
"    
[1] ";   
pFun(); //B::Bf();

pFun =
(Fun)pVtab[11][2];

cout <<
"    
[2] ";

cout << pFun
<< endl;

cout <<
"[12] B::ib = ";

cout <<
*((int*)(&dd)+12) << endl; //B::ib

cout <<
"[13] B::cb = ";

cout << (char)*((int*)(&dd)+13) <<endl;//B::cb

下面给出运行后的结果(分VC++和GCC两部份)


GCC 3.4.4


VC++ 2003


[0] B1::_vptr
->

    [0] : D::f()

    [1] : D::f1()

    [2] : B1::Bf1()

    [3] : D::f2()

    [4] : D::Df()

    [5] : 1

[1] B1::ib1 :
11

[2] B1::cb1 :
1

[3] B2::_vptr
->

    [0] : D::f()

    [1] : D::f2()

    [2] : B2::Bf2()

    [3] : 0

[4] B2::ib2 :
12

[5] B2::cb2 :
2

[6] D::id :
100

[7] D::cd :
D

[8] B::_vptr
->

    [0] : D::f()

    [1] : B::Bf()

    [2] : 0

[9] B::ib :
0

[10] B::cb :
B

[11] NULL :
0


[0]
D::B1::_vptr->

     [0] D::f1()

     [1] B1::Bf1()

     [2] D::Df()

     [3] 00000000

[1] =
0x0013FDC4 
? 该地址取值后是-4

[2] B1::ib1 =
11

[3] B1::cb1 =
1

[4]
D::B2::_vptr->

     [0] D::f2()

     [1] B2::Bf2()

     [2] 00000000

[5] =
0x4539260  
? 该地址取值后是-4

[6] B2::ib2 =
12

[7] B2::cb2 =
2

[8] D::id =
100

[9] D::cd =
D

[10]  = 0x00000000

[11]
D::B::_vptr->

     [0] D::f()

     [1] B::Bf()

     [2] 00000000

[12] B::ib =
0

[13] B::cb =
B

关于虚拟继承的运行结果我就不画图了(前面的作图已经让我产生了很严重的厌倦感,所以就偷个懒了,大家见谅了)

在上面的输出结果中,我用不同的颜色做了一些标明。我们可以看到如下的几点:

1)无论是GCC还是VC++,除了一些细节上的不同,其大体上的对象布局是一样的。也就是说,先是B1(黄色),然后是B2(绿色),接着是D(灰色),而B这个超类(青蓝色)的实例都放在最后的位置。

2)关于虚函数表,尤其是第一个虚表,GCC和VC++有很重大的不一样。但仔细看下来,还是VC++的虚表比较清晰和有逻辑性。

3)VC++和GCC都把B这个超类放到了最后,而VC++有一个NULL分隔符把B和B1和B2的布局分开。GCC则没有。

4)VC++中的内存布局有两个地址我有些不是很明白,在其中我用红色标出了。取其内容是-4。接道理来说,这个指针应该是指向B类实例的内存地址(这个做法就是为了保证重复的父类只有一个实例的技术)。但取值后却不是。这点我目前还并不太清楚,还向大家请教。

5)GCC的内存布局中在B1和B2中则没有指向B的指针。这点可以理解,编译器可以通过计算B1和B2的size而得出B的偏移量。

时间: 2024-08-01 17:47:28

C++对象内存模型的相关文章

C#的对象内存模型

转载自:http://www.cnblogs.com/alana/archive/2012/07/05/2577893.html C#的对象内存模型: 一.栈内存和堆内存1.栈内存 由编译器自动分配和释放,主要用来保存一些局部变量.函数的参数等,例如,int a = 10 ,那么编译器会自动在栈上开辟一块内容用来存储变量a.2.堆内存 由程序员手动申请和释放,在C++中,通过new关键字申请,编译器不会释放,必须通过delete释放,对于C#,通过new 关键字申请,因为编译器的垃圾回收机制,程

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

对象内存模型 一. 栈(Stack) VS. 堆(heap) 栈 由系统自动管理,以执行函数为单位 空间大小编译时确定(参数+局部变量) 函数执行时,系统自动分配一个stack 函数执行结束时,系统立即自动回收stack 堆  在c++中由程序员手动控制 手动分配new和malloc 手动释放delete和free 具有全局性,总体无大小限制 容易造成内存泄露 1. Myclass c(10); // 栈对象,空间大小在编译时确定,函数执行结束,系统立即回收 2. Myclass* func()

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

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

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

从例子入手,考察如下带有虚函数的类的对象内存模型: 1 class A { 2 public: 3 virtual void vfunc1(); 4 virtual void vfunc2(); 5 void func1(); 6 void func2(); 7 virtual ~A(); 8 private: 9 int m_data1, m_data2; 10 }; 11 12 class B : A { 13 public: 14 virtual void vfunc1();; 15 vo

(转)c#对象内存模型

对象内存模型 C#的对象内存模型写这篇博客的主要目的是为了加深自己的理解,如有不对的地方,请各位见谅. C#的对象内存模型: 一.栈内存和堆内存1.栈内存 由编译器自动分配和释放,主要用来保存一些局部变量.函数的参数等,例如,int a = 10 ,那么编译器会自动在栈上开辟一块内容用来存储变量a.2.堆内存 由程序员手动申请和释放,在C++中,通过new关键字申请,编译器不会释放,必须通过delete释放,对于C#,通过new 关键字申请,因为编译器的垃圾回收机制,程序员不需要手动释放内存.例

极客班直播课笔记1 C++对象内存模型(堆栈模型)

对象内存模型 一. 栈(Stack) VS. 堆(heap) 栈 由系统自动管理,以执行函数为单位 空间大小编译时确定(参数+局部变量) 函数执行时,系统自动分配一个stack 函数执行结束时,系统立即自动回收stack 堆  在c++中由程序员手动控制 手动分配new和malloc 手动释放delete和free 具有全局性,总体无大小限制 容易造成内存泄露 1. Myclass c(10); // 栈对象,空间大小在编译时确定,函数执行结束,系统立即回收 2. Myclass* func()

c++对象内存模型之虚析构函数篇(1)

看了两篇关于c++对象内存模型的文章,来源在这里: http://blog.csdn.net/haoel/article/details/3081328/ http://blog.csdn.net/haoel/article/details/3081385 文章中讲了多种继承模式中虚函数的实际情况,按我的理解是把单一继承理解好了,其它几种只是一种变种.当然没这文章,我断想不到c++对象内存是这个样子. 文章中讲的情况,唯独没有讲有虚析构函数存在的情形.我学着文章中介绍的方法,用试探的方式找有虚析

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

MemoryLayout 基本使用方法 HandyJSON 是 Swift 处理 JSON 数据的开源库之一,类似 JOSNModel,它可以直接将 JSON 数据转化为类实例在代码中使用. 由于 Swift 是一种静态语言,没有 OC 那种灵活的 Runtime 机制,为了达到类似 JSONModel 的效果,HandyJSON 另辟蹊径,绕过对 Runtime 的依赖,直接操作实例的内存对实例属性进行赋值,从而得到一个完全初始化完成的实例. 本文将通过探究 Swift 对象内存模型机制,简单

C++学习笔记----4.5 C++继承时的对象内存模型

推荐阅读:http://blog.csdn.net/randyjiawenjie/article/details/6693337 最近研究了一下,C++继承的内存对象模型.主要是读了读http://blog.csdn.net/haoel/article/details/3081328(C++ 对象的内存布局).很推荐这篇文章. 对这篇文章做了做总结.本文的大部分内容来自于这篇文章中的总结http://blog.csdn.net/haoel/article/details/3081328(C++

c++对象内存模型【内存布局】

出处:http://www.cnblogs.com/kekec/archive/2013/01/27/2822872.html #类中的元素 0. 成员变量   1. 成员函数   2. 静态成员变量   3. 静态成员函数   4. 虚函数   5. 纯虚函数 #影响对象大小的因素 0. 成员变量     1. 虚函数表指针(_vftptr)   2. 虚基类表指针(_vbtptr)   3. 内存对齐 _vftptr._vbtptr的初始化由对象的构造函数, 赋值运算符自动完成:对象生命周期