您的位置:首页 > 编程语言 > C语言/C++

C++类的对象空间

2013-10-15 21:56 459 查看
一、简单对象的存储

1、 基本类型对齐原则:

Char
1

Short
2

Int
4

Long
4

Float
4

Double
8

2、 结构体类型对齐原则:(参见《结构体对齐》一文)

以最大成员类型的对齐方式为准,即当需要增长时,增长最大成员类型所占用的字节数。

3、 静态成员变量不占用类对象的存储空间原则static

静态成员变量所有的类对象共享一份,在静态区域中,并不占用类对象的空间。

4、 没有任何成员变量的类对象占用一个字节的空间

对于没有任何成员变量的类(空类),其实它并不是空的,它隐含着被编译器添加了一个char。因为实例化的原因(空类同样可以被实例化),每个实例在内存中都必须有一个独一无二的地址。因此,编译器会给一个空类隐含的加一个字节,使空类在实例化后在内存得到独一无二的地址。

验证程序:vc.net2003下运行结果

#include
<iostream>

using
namespace std;

/*没有任何数据成员的对象类占用一个字节的空间*/-----规则4

class
A1

{

};

/*静态数据成员不占用类对象的存储空间*/-----规则3

class
A2

{

char c1;

static
int count;

};

/*当只有char类型时,以1个字节为单位对齐*/----规则1

class
B1

{

char c1;

char c2;

};

/*与A比较发现,当最大为short时,以2个字节为单位对齐*/----规则1

class
B2

{

char c1;

short s;

};

/*与A比较发现,当最大为int时,以4个字节为单位对齐*/----规则1

class
B3

{

char c1;

int i;

};

/*与A比较发现,当最大为double时,以8个字节为单位对齐*/-----规则1

class
B4

{

char c1;

float d;

};

/*与A比较发现,当最大为double时,以8个字节为单位对齐*/----规则1

class
B5

{

char c1;

double d;

};

/*c
s i 占4个字节,d占8个字节*/----规则1

class
C1

{

char c;

short s;

int i;

double d;

};

/*d占4个字节,c
s i 占4个字节*/----规则1

class
C2

{

double d;

char c;

short s;

int i;

};

/*c占1个字节,d从下一个4字节开始占4个字节,s
i在下一个4字节中*/----规则1

class
C3

{

char c;

double d;

short s;

int i;

};

/*c
s 在头4个字节中,d占下四个字节,i 在最后4个字节中*/-----规则1

class
C4

{

char c;

short s;

double d;

int i;

};

int
main()

{

cout << "size of A1 : " << sizeof(A1) << endl; /*1字节*/

cout << "size of A2 : " << sizeof(A2) << endl; /*1字节*/

cout << endl;

cout << "size of B1 : " << sizeof(B1) << endl; /*2字节*/

cout << "size of B2 : " << sizeof(B2) << endl; /*4字节*/

cout << "size of B3 : " << sizeof(B3) << endl; /*8字节*/

cout << "size of B4 : " << sizeof(B4) << endl; /*8字节*/

cout << "size of B5 : " << sizeof(B5) << endl; /*16字节*/

cout << endl;

cout << "size of C1 : " << sizeof(C1) << endl; /*16字节*/

cout << "size of C2 : " << sizeof(C2) << endl; /*16字节*/

cout << "size of C3 : " << sizeof(C3) << endl; /*24字节*/

cout << "size of C4 : " << sizeof(C4) << endl; /*24字节*/

system("pause");

return 0;

}

二、一般继承下的对象存储

1、虚表指针占用4个字节原则

①对于一个类而言,在不存在虚函数的情况下,类的大小等于成员大小之和(按照对其原则);②当存在虚拟函数时,由于要保存虚表指针,故多占用4个字节。

2、子类共享父类的虚表指针原则

在普通继承下,子类与父类共享一个虚表,子类不需要另外添加内存。

3、虚基类表指针占用4字节原则

①在虚继承的情况下,继承了多个继承了同一个父类的中间类的子类只保存了一个同他基类的备份;②但每个中间类都需要需要保存指向基类表的指针来指向共同的基类。

#include
<iostream>

using
namespace std;

class
A1

{

int numA1;

};

/*与A1比较,存在虚函数的情况下,需要保存虚函数表指针占4个字节*/----规则1-②

class
A2

{

int numA2;

virtual FunA2();

};

/*与A2比较,当不存在不同于父类的虚函数时,子类与父类共享保存虚函数表的指针*/----规则2

class
B1 : A2

{

int numB1;

};

/*与A2比较,当存在不同于父类的虚函数时,子类与父类共享保存虚函数表的指针*/----规则2

class
B2 : A2

{

int numB2;

virtual FunB2();

};

/*与B1比较,虚继承需要保存指向虚基类表的指针占4个字节*/----规则3

class
B3 : virtual A2

{

int numB3;

};

/*虚继承下,若子类中有不同于父类的虚函数,则需要不同于父类的指向虚函数表的指针*/----规则3

class
B4 : virtual A2

{

int numB4;

virtual FunB4();

};

/*虚继承下,共同的基类只有一个备份,但每个虚继承的类中多了一个只想那个虚基类表的指针*/----规则3-②

class
C1 : B3, B4

{

int numC1;

};

void
main()

{

cout << "sizeof A1 is : " << sizeof(A1) << endl; /* 4 */

cout << "sizeof A2 is : " << sizeof(A2) << endl; /* 8 */

cout << endl;

cout << "sizeof B1 is : " << sizeof(B1) << endl; /* 12 */

cout << "sizeof B2 is : " << sizeof(B2) << endl; /* 12 */

cout << "sizeof B3 is : " << sizeof(B3) << endl; /* 16 */

cout << "sizeof B4 is : " << sizeof(B4) << endl; /* 20 */

cout << endl;

cout << "sizeof C1 is : " << sizeof(C1) << endl; /* 32 */

system("pause");

}

C的内存分布:

B3指向虚基类表的指针
4

B3自己的存储区域
4

B4自己的指向虚函数表的指针
4

B4指向虚基类表的指针
4

B4自己的存储区域
4

C1自己的存储区域
4

基类的存储区域
8

ps:本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/bxhj3014/archive/2008/10/04/3015795.aspx

一个类的实例化对象所占空间的大小?
注意不要说类的大小,是类的对象的大小. 首先,类的大小是什么?确切的说,类只是一个类型定义,它是没有大小可言的。 用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小。 如果 Class A; A obj; 那么sizeof(A)==sizeof(obj)

那么sizeof(A)的大小和成员的大小总和是什么关系呢?很简单,一个对象的大小大于等于所有非静态成员大小的总和。

为什么是大于等于而不是正好相等呢?超出的部分主要有以下两方面:

1)
C++对象模型本身

对于具有虚函数的类型来说,需要有一个方法为它的实体提供类型信息(RTTI)和虚函数入口,常见的方法是建立一个虚函数入口表,这个表可为相同类型的对象共享,因此对象中需要有一个指向虚函数表的指针,此外,为了支持RTTI,许多编译器都把该类型信息放在虚函数表中。但是,是否必须采用这种实现方法,C++标准没有规定,但是这几户是主流编译器均采用的一种方案。

2)
编译器优化

因为对于大多数CPU来说,CPU字长的整数倍操作起来更快,因此对于这些成员加起来如果不够这个整数倍,有可能编译器会插入多余的内容凑足这个整数倍,此外,有时候相邻的成员之间也有可能因为这个目的被插入空白,这个叫做“补齐”(padding)。所以,C++标准紧紧规定成员的排列按照类定义的顺序,但是不要求在存储器中是紧密排列的。

基于上述两点,可以说用sizeof对类名操作,得到的结果是该类的对象在存储器中所占据的字节大小,由于静态成员变量不在对象中存储,因此这个结果等于各非静态数据成员(不包括成员函数)的总和加上编译器额外增加的字节。后者依赖于不同的编译器实现,C++标准对此不做任何保证。

3)C++标准规定类的大小不为0,空类的大小为1。当类不包含虚函数和非静态数据成员时,其对象大小也为1。

4)如果在类中声明了虚函数(不管是1个还是多个),那么在实例化对象时,编译器会自动在对象里安插一个指针指向虚函数表VTable,在32位机器上,一个对象会增加4个字节来存储此指针,它是实现面向对象中多态的关键。而虚函数本身和其他成员函数(构造函数,析构函数等)一样,是不占用对象的空间的。

我们来看下面一个例子:(此例子在Visual
C++编译器中编译运行)

#include
using namespace std;

class
A { };

class
B {

char ch;

void func() { }

};

class
C {

char ch1; //占用1字节

char ch2; //占用1字节

virtual void func() { }

};

class
D {

int in;

virtual void func() { }

};

void
main() {

A
a;

B
b;

C
c;

D
d;

cout<<sizeof(a)<<endl;//result=1

cout<<sizeof(b)<<endl;//result=1 //对象c扩充为2个字,但是对象b为什么没扩充为1个字呢(空类的对象一个字节,含一个char的类类对象也为一个字节。)?因为B类只有一个成员变量,普通成员函数不占用内存。

cout<<sizeof(c)<<endl;//result=8

//对象c实际上只有6字节有用数据,但是按照上面第二点编译器优化,编译器将此扩展为两个字,即8字节

cout<<sizeof(d)<<endl;//result=8

}

综上所述:

一个类中,虚函数、成员函数(包括静态与非静态)和静态数据成员都是不占用类对象的存储空间的。

对象大小=
vptr(可能不止一个,这个很难确定,不过试过,类中定义了一个virtual函数,仍然为占用4个字节) + 所有非静态数据成员大小 + Aligin字节大小(依赖于不同的编译器)

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lichunming83/archive/2009/10/31/4752674.aspx

三、虚继承下的对象存储

首先,先看下面几个类:

class X{};

class Y : public virtual X {};

class Z : public virtual X {};

class A : public virtual Y {};

class B : public Y, public Z{};

class C : public virtual Y, public virtual Z {};

class D : public virtual C{};

在VC6.0上执行的结果为为:

sizeof(X):1

sizeof(Y):4

sizeof(Z):4

sizeof(A):8

sizeof(B):8

sizeof(C):12

sizeof(D):16

(1)首先,对于class X,其实它并不是空的,它隐含着被编译器添加了一个char,那么为什么要添加这个char
?我们来看,有这样的定义X x,则x肯定是有地址的,那既然x有地址,OK,sizeof(x)肯定就不为0了撒。
为什么会出现这种结果呢?初学者肯定会很烦恼是吗?类X明明是空类,它的大小应该为0,为什么编译器输出的结果为1呢?这就是因为实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以X的大小为1。

(2)对于class
Y和class
Z,都有额外的负担,那么这个负担反映在指向virtual
base class subobject(虚基类对象)上的指针。那照这样说,sizeof(Y)就应该等于5(先不考虑字节对齐)了?其实有些编译器提供了一些特殊处理,这使得一个empty
virtual base class(空虚基类)被视为derived
class object最开头的一部分,也就是说它并没有花费任何空间,即这个1bytes被省了。

(3)同样,对于class A,本身base
class Y的大小为4,加个指针,则为8了。

(4)对于class B,也很简单,class
Y和class Z的大小都为4。

(5)现在来看class C,因为他虚继承了Y和Z,所以它应该添加2个指针,在加上Y和Z,不应该是16么,那为什么结果是12呢?

我们可以看到,Y和Z都是虚派生自class
X,而一个virtual
base class subobject只会在derived
class中存在一份实体,不管它在class继承体系中出现了多少次。

如果说我把之前的类重新改下,新添加一个“class X1
{}; ”,而把class
Z改成“class Z : public
virtual X1 {}; ”,那么这个时候sizeof(C)就等于16了。

(6)最后对于class D来说,应该很简单了。

再看下面的类定义:

class A

{

int a;

}

sizeof(A) 其值为4,而int 型大小为4;为什么没有多一个插进去的char的大小?

为什么没有隐含加一个字节呢,那是因为成员变量int a 已经可以占4个字节了,也就是在内存中分配了4个字节的空间,所以没有必要隐含加一个字节也可以实例化了(只要分配了内存空间,也就是实例化了,这就是实例化的定义:类的实例化就是在内存中分配一块地址)。

最后看一个类定义:



1

#include <cstdlib>

2

#include <iostream>

3

#include <memory>

4


5

using namespace std;

6

class A

7

{

8

char k[3];

9

public:

10

virtual void aa(){};

11

};

12


13

class B : public virtual A

14

{

15

char j[3];

16

public:

17

virtual void bb(){};

18

};

19


20

class C : public virtual B

21

{

22

char i[3];

23

public:

24

virtual void cc(){};

25

};

26


27

int main(int argc, char *argv[])

28

{

29

cout<<"sizeof(A):"<<sizeof(A)<<endl;

30

cout<<"sizeof(B):"<<sizeof(B)<<endl;

31

cout<<"sizeof(C):"<<sizeof(C)<<endl;

32

system("PAUSE");

33

return EXIT_SUCCESS;

34

}
问题:程序运行的结果?

答案:8,16,24。

解释:

(1)对于类A,由于有一个虚函数,那么必须得有一个对应的虚函数表来记录对应的函数入口地址。每个地址需要一个虚指针,指针的大小为4。类中还有一个char k[3],当然大小为3。为什么是8呢?因为在计算机里,是以4为单位,所以第一条输出的结果为8。

(2)对于类B,同类A一样,自己的大小为8,但是由于虚继承类A,所以在虚表中要加入一个虚类指针来指向其类A,然后在包含类A的所有成员,sizeof(A)为8,结果便是16。

(3)对于类C,同类B一样,自己的大小8,加上sizeof(B),结果为24。

虚继承就是为了节约内存的,他是多重继承中的特有的概念。适用与菱形继承形式。(详细概念参见:《【C/C++】概念:虚继承与虚基类的本质》)

虚函数继承就是覆盖。即基类中的虚函数被派生类中的同名函数所覆盖。
是实现多态的方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: