C++ 内存详解(二)

来源:互联网 发布:sql怎么用别名查询 编辑:程序博客网 时间:2024/06/05 23:22

在本文中主要是介绍C++中类在实例化过程中在内存中的分配。

很多人都知道C++类是由结构体发展而来,所以他们的成员变量(C语言的结构体只有成员变量,C++的结构体和类基本相同,除了默认成员变量的属性,类成员变量默认私有,结构体成员变量默认公有,C++保留结构体主要是兼容C程序)的内存分配机制是一样的。下面我们以类来说明问题,如果类搞清楚了,结构体也就明白了。类分为成员变量和成员函数,我们先讨论成员变量。一个类对象的地址就是类所包含的这一片内存空间的首地址,这个首地址也就对应具体某一成员变量地址。(在定义成员对象的同时,这些成员变量也就被定义了)我们一段代码说明问题:

类定义:

class K{   public:         K(){k = 12;}         ~K(){}         int k;}; 
类的使用:

 K kTemp; printf("%d--%d\n",&kTemp,&kTemp.k); printf("%d--%d\n",sizeof(K),sizeof(kTemp.k)); int *i = (int*)(&kTemp); int w = *i; printf("%d\n",w);
上面代码运行的结果为:

1310588--1310588
4--4
12

很明显,类的内存大小和其唯一的成员变量的内存大小是一致的。内存地址也是一致的。他们甚至是可以相互转换的。换成结构体结果也是一样的。这个时候问题来了:成员函数呢?上面的代码就好像没有成员函数一样,那是因为所有的成员函数都是存放在代码区的,不管全局函数,还是成员函数。要是成员函数占用类的对象空间,那将是多么可怕的一件事:定义类对象就有成员函数占用一段空间。我们再来补充一下静态成员函数的存放问题:静态成员函数与一般的成员函数的唯一区别就是没有this指针,因此不能访问非静态数据成员,就像我们前面提到的,所有函数都存放在代码区,静态函数也不例外。

C++是一种面向对象的编程语言,它向下保存了对C的兼容,同时也允许程序员能够自由的操控内存,虽然会带来一些问题,但是这不是本文讨论的问题。类是对某种对象的定义,包括变量和方法,也可以理解为现实生活中一类具有共同特征的事物的抽象,它是面向对象语言的基础。所以类是不占有内存的。但是如果类生成实例,那么将会在内存中分配一块内存来存储类对象。

类的实例化对象在内存中是如何分配内存的,请看下面的类:

class A {};

从形式上看,它似乎什么也没有,事实上它不知隐含了一个构造函数和一个析构函数,hi阿尤一些操作重载运算函数,比如”=“。如果A被实例化,例如:A a;在内存会占据多大的空间?有人可能说是4,也有说是0,但是正确的答案是:1,为什么是1?原因很多,比如说如我定义一个数组A[10];如果按上面说的是0,这样的局面会很尴尬,所以A这样一个空类,编译器给它一个字节来填充。

如果我们增加一个变量,(字节对齐默认为4):

   class  A   {     public:       int i;   }
类A的实例将占据4个字节的内存,sizeof(A)=4,变量i的初值被编译器指定为0xcdcdcdcd。

如果再增加一个变量:

   class A   {      public:      int  i;      int  l;   }
此时,按章变量生命的先后顺序,i被放在了低地址上,l紧跟其后。实例对象占用8个字节,sizeof(A)=4+4=8。

如果类里面包含函数:

  class A {     public:      int i;      int l;      int add(int x,int y){return (x+y);} };
这时,有人会可能会说类的大小为12,事实上sizeof(A)=8;

为什么会这样?这是因为sizeof访问程序的数据段,而函数地址责备保存在代码段内,所以最后结果是8。

再看下面这个情况:

class A {      public:         int i;         int l;         static int s;         int add(int x,int y){return (x+y)}; };
此时sizeof(A)大小仍是8,这是因为static变量在全局区(静态区),而非数据段

当然,类里面含有虚函数时,情况就另当别论了!例如:

class A {      public:         int i;         int l;         static int s;         virtual void Say(){};         int add(int x,int y){return (x+y)}; };
因为含有虚函数,所以类里面将含有一个虚指针vptr,指向该类的虚表vtbl,一个指针占用四字节的地址,所以sizeof(A)=12,虚指针放在类实例地址的最低位置,比如:A *a=new A;我们可以这样给变量i赋值:

 int *p = (int *)a; p++; *p = 1;//把i的值赋为1.


如果类作为派生类,内存将如何分配呢?

这种情况虽然有些复杂,但并不是说不好理解,它有多少个父类每个父类的大小加起来再加上自身就是sizeof的大小。

总结C++类对象内存结构:

首先介绍一下C++中有继承关系的类对象内存的布局:

在C++中,如果类中有虚函数,那么它就有一个虚函数表的指针——vfptr,在类对象最开始的内存数据中,之后是类中的成员变量的内存数据。

对于子类,最开始的内存数据记录着父类对象的拷贝(包括父类虚函数表指针和成员变量),之后是子类自己的成员变量数据。

对于子类的子类,同样的原理,但是无论继承多少个子类,对象中始终只有一个虚函数表指针。






0 0
原创粉丝点击