分享

c++对象内存结构

 夜下月光 2014-05-19

对任何一个普通C++程序来讲,它都会涉及到5种不同的数据段。常用的几个数据段种包含有“程序代码段”、“程序数据段”、“程序堆栈段”等。不错,这几种数据段都在其中,但除了以上几种数据段之外,进程还另外包含两种数据段。下面我们来简单归纳一下进程对应的内存空间中所包含的5种不同的数据区。

代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。

数据段:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。

BSSBSS段包含了程序中未初始化全局变量,在内存中bss段全部置零。

heap):堆是用于存放进程运行中被动态分配的内存段,它大小并不固定,可动态扩张或缩减。当进程调用malloc/new等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

:栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味这在数据段中存放变量)。除此以外在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也回被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上将我们可以把堆栈看成一个临时数据寄存、交换的内存区。

 

 

c++对象的内存布局(

 

1. 空类:

 

class c000

{

}; // sizeof = 1

    它的大小为1字节,这是一个占位符,我们可以看到它的值是0xcc。在debug模式下,这表示是由编译器插入的调试代码所初始化的内存。在release模式下可能是个随机值。

    这个字节保证c000类的实例对象拥有不同的内存地址。&a!=&b

 

 

2. 仅有普通成员变量的类

 

class c001

{

int a;

}; // sizeof = 4

 

指针4字节,

(unsigned) char1字节,

(unsigned) short2字节,

(unsigned) int 4字节,

(unsigned) long4字节,

float4字节,

double8字节。

static成员变量存储在全局数据区,并不占用中的位置,因此不被sizeof计算在内。

 

class c002

{

char c1[2]; // sizeof = 1

char c2[2]; // sizeof = 1

int i[2]; // sizeof = 4

}; // sizeof = 12

 

class c003

{

char c1[2]; // sizeof = 1

int i[2]; // sizeof = 4

char c2[2]; // sizeof = 1

}; // sizeof = 16

 

class c004

{

  double d; // sizeof = 8

  int i; // sizeof = 4

}; // sizeof = 16

 

class c005

{

  char c[11]; // sizeof = 1

  double d; //sizeof = 8

}; // sizeof = 24

 

    要注意成员变量在类中的声明顺序也就是该变量在内存中的存放顺序。(内存对齐)

    这里采用了内存对齐。cpu的优化规则大致原则是这样的:对于n字节的元素(n=2,4,8…),它的首地址能被n整除,才能获得最好的性能。设计编译器的时候可以遵循这个原则:对于每一个变量,可以从当前位置向后找到第一个满足这个条件的地址作为首地址。

    注意的是,即便采用这个原则,c002得到的结果也应该为13。但是结构体一般会面临数组分配的问题。编译器为了优化这种情况,干脆把它的大小设为14,这样就没有麻烦了。否则的话,会出现单个对象的大小为13,而大小为n的对象数组大小却为14*(n-1)+13的尴尬局面。(如果让你设计编译器,你将怎样解决内存对齐的问题。)

 

 

3. 有普通成员变量与普通成员函数的类

 

class c006

{

  int set(){return 1;};

  static int foo(){return 2;}

}; // sizeof = 1

 

class c007

{

short m; // sizeof = 2

float n[3]; // sizeof = 4

int set(){};

static int sfoo() { return 1; }

}; // sizeof = 16

 

    普通成员函数、静态成员函数、静态成员变量皆不会在类的对象中有所表示

    成员函数和对象的关联由编译器在编译时处理,编译器会在编译时决议出正确的普通成员函数地址,并将对象的地址以this指针的方式,做为第一个参数传递给普通成员函数,以此来进行关联。

    静态成员函数类似于全局函数,不和具体的对象关联。静态成员变量也一样。静态成员函数和静态成员变量和普通的全局函数及全局变量不同之处在于它们多了一层名字限定。

 

 

4. 基类为空且派生类为空

 

class base

{

}; // sizeof = 1

 

class derived: public base

{

}; // sizeof = 1

 

  

5. 基类为空且派生类不为空

 

class base

{

}; // sizeof = 1

 

class derived: public base

{

  float f;

  char c[11];

  int foo(){return 4;};

}; // sizeof = 16

 

 

6. 基类不为空且派生类为空

 

class base

{

float f;

char c[11];

int foo(){return 4;};

}; // sizeof = 16

 

class derived: public base

{

}; // sizeof = 16

 

 

7. 基类不为空且派生类不为空

 

class base

{

float f;

char c[11];

public:

int foo(){return 4;};

}; // sizeof = 16

 

class derived: private base

{     

float f1;

char c1[11];

public:

int foo1(){return 4;};

}; // sizeof = 32

    基类的成员变量悉数被派生类继承,并且与继承方式(公有或私有)无关,如derived是私有继承自base。继承方式只影响数据成员的能见度。派生类对象中属于从基类继承的成员变量由基类的构造函数初始化。通常会调用默认构造函数,除非派生类在它的构造函数初始化列表中显式调用基类的非默认构造函数。如果没有指定,而父类又没有缺省构造函数,则会产生编译错误。

 

 

8. 多层继承

 

class level_1

{

private:

char c[3];

public:

level_1()

{

c[0] = 0x00;

c[1] = 0x01;

c[2] = 0x02;

};

void set()

{

  c[0] = 0x00;

  c[1] = 0x00;

  c[2] = 0x00;

};

}; // sizeof = 3

 

class level_2: public level_1

{

private:

int f;

public:

level_2()

{

  f = 7;

};

void set()

{

  f = 0;

};

}; // sizeof = 8

 

class level_3: public level_2

{

private:

short s[5];

public:

level_3()

{

  for(int i = 0;i<5;i++)

   s[i] = i;

};

void set()

{

for(int i = 0;i<5;i++)

  s[i] = 0x400;

};

}; // sizeof = 20

输出结果为:

the size of level_1 is 3

the detail of level_1 is 00 01 02

the size of level_2 is 8

the detail of level_2 is 00 01 02 cc 07 00 00 00

the size of level_3 is 20

the detail of level_3 is 00 01 02 cc 07 00 00 00 00 00 01 00 02 00 03 00 04 00 cc cc

    因此关于多层继承,派生类的对象布局为基类中的数据成员根据内存对齐加上派生类中的数据成员,顶层类在前

 

 

9. 多重继承

 

    8中的例子修改为:

class level_1{…};

class level_2{…};

class level_3: public level_1, public level_2{…};

    只修改继承关系,将多层继承转化为多重继承,实现不变。那么输出结果为:

the size of level_3 is 20

the detail of level_3 is 00 01 02 cc 07 00 00 00 00 00 01 00 02 00 03 00 04 00 cc cc

    由此可见,关于多重继承,派生类的对象布局仍为基类中的数据成员根据内存对齐加上派生类中的数据成员,左基类在前

 

 

10. 多层继承与多重继承

class l1

{

char c1[2];

public:

l1(){c1[0] = 0x01;c1[1] = 0x02;};

}; // sizeof = 2

 

class l2

{

int i2[2];

public:

l2(){i2[0] = 7;i2[1] = 8;};

}; // sizeof = 8

 

class l3:public l1,public l2

{

char c3[2];

public:

l3(){c3[0] = 0x04;c3[1] = 0x05;};

}; // sizeof = 16

 

class l4:public l2

{

int i4[2];

public:

l4(){i4[0] = 9;i4[1] = 10;};

}; // sizeof = 16

 

class l5:public l3,public l4

{

char c5;

public:

l5(){c5 = 0x0a;};

}; // sizeof = 36

 

输出结果为:

the size of l1 is 2

the detail of l1 is 01 02

the size of l2 is 8

the detail of l2 is 07 00 00 00 08 00 00 00

the size of l3 is 16

the detail of l3 is 01 02 cc cc 07 00 00 00 08 00 00 00 04 05 cc cc

the size of l4 is 16

the detail of l4 is 07 00 00 00 08 00 00 00 09 00 00 00 0a 00 00 00

the size of l5 is 36

the detail of l5 is 01 02 cc cc 07 00 00 00 08 00 00 00 04 05 cc cc 07 00 00 00

08 00 00 00 09 00 00 00 0a 00 00 00 0a cc cc cc

    在继承层次中某一位置的某派生类,其实例对象的内存中,存储着从左基类开始的各个基类的内存内容,最后是自己的内存内容。这是一个递归的过程。类似于向基类方向宽度优先遍历继承树。

 

引用自http://blog.163.com/niwei_258/blog/static/106284882010930112026534/

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多