一个类,有成员变量:静态与非静态之分;而成员函数有三种:静态的、非静态的、虚的。
那么这些个东西在内存中到底是如何分配的呢?
以一个例子来说明:
- <span style="font-size:16px;"></span>
{
public:
static int a;
CObject();
~CObject();
void Fun();
private:
int m_count;
int m_index;
};
这是我的一段测试代码,
运行结果是:
Sizeof(CObject):8
CObject::a=1
Construct!
sizeof(myObject):8
sizeof(int)4
Destruct!
我有疑问如下:
(1)C++中,应该是对象才会被分配内存空间吧??为什么CObject内存大小是8,刚好和两个成员变量的大小之和一致!难道还没实例化的时候,类就 已经有了内存空间了?
(2)当对象生成了之后,算出的内存大小怎么还是8,函数难道不占用内存空间吗?至少应该放个函数指针在里面的吧?内存是怎样布局的?
(3)静态成员应该是属于类的,怎么类的大小中没有包含静态成员的大小?
下面分别解答如下:
1)Sizeof(CObject)是在编译时就计算了的,一个类定义了,它所占的内存编译器就已经知道了,这时只是得到它占用的大小,并没有分配内存操作 。也可以这样想:编译器肯定知道大小了,这与分配内存空间无关,知道大小了,以后实例化了才能知道要分配多大。
2)类的普通成员函数、静态成员函数是不占类内存的,至于你说的函数指针在你的类中有虚函数的时候存在一个虚函数表指针,也就是说如果你的类里有虚函数则 sizeof(CObject)的值会增加4个字节。
其实类的成员函数 实际上与 普通的全局函数一样。
只不过编译器在编译的时候,会在成员函数上加一个参数,传入这个对象的指针。
成员函数地址是全局已知的,对象的内存空间里根本无须保存成员函数地址。
对成员函数(非虚函数)的调用在编译时就确定了。
像 myObject.Fun() 这样的调用会被编译成形如 _CObject_Fun( &myObject ) 的样子。
函数是不算到sizeof中的,因为函数是代码,被各个对象共用,跟数据处理方式不同。对象中不必有函数指针,因为对象没必要知道它的各个函数的地址(调 用函数的是其他代码而不是该对象)。
类的属性是指类的数据成员,他们是实例化一个对象时就为数据成员分配内存了,而且每个对象的数据成员是对立的,而成员函数是共有的~
静态成员函数与一般成员函数的唯一区别就是没有this指针,因此不能访问非静态数据成员。总之,程序中的所有函数都是位于代码区的。
3)静态成员并不属于某个对象,sizeof取的是对象大小。
知道了上面的时候,就可以改一下来看看:
我也补充一些:
class CObject
{
public:
static int a;
CObject();
~CObject();
void Fun();
private:
double m_count; //这里改成了double
int m_index;
};
这个类用sizeof()测出来的大小是 2*sizeof(double)=16
class CObject
{
public:
static int a;
CObject();
~CObject();
void Fun();
private:
char m_count; //这里改成了char
int m_index;
};
大小是2*sizeof(int)=8
class CObject
{
public:
static int a;
CObject();
~CObject();
void Fun();
private:
double m_count; //这里改成了double
int m_index;
char c;
};
sizeof(char)+sizeof(int) <sizeof(double) 所以大小是2*sizeof(double)
其实这里还有一个是内存对齐的问题。
空类大小是1。
另外要注意的一些问题:
先看一个空的类占多少空间?
- class Base
- {
- public:
- Base();
- ~Base();
- };
class Base { public: Base(); ~Base(); };
注意到我这里显示声明了构造跟析构,但是sizeof(Base)的结果是1.
因为一个空类也要实例化,所谓类的实例化就是在内存中分配一块地址,每个实例在内存中都有独一无二的地址。同样空类也会被实例化,所以编译器会给空类隐含 的添加一个字节,这样空类实例化之后就有了独一无二的地址了。所以空类的sizeof为1。
而析构函数,跟构造函数这些成员函数,是跟sizeof无关的,也不难理解因为我们的sizeof是针对实例,而普通成员函数,是针对类体的,一个类的成 员函数,多个实例也共用相同的函数指针,所以自然不能归为实例的大小,这在我的另一篇博文有提到。
接着看下面一段代码
- class Base
- {
- public:
- Base();
- virtual ~Base(); //每个实例都有虚函数表
- void set_num(int num) // 普通成员函数,为各实例公有,不归入sizeof统计
- {
- a=num;
- }
- private:
- int a; //占4字节
- char *p; //4字节指针
- };
- class Derive:public Base
- {
- public:
- Derive():Base(){};
- ~Derive(){};
- private:
- static int st; //非实例独占
- int d; //占4字节
- char *p; //4字节指针
- };
- int main()
- {
- cout<<sizeof(Base)<<endl;
- cout<<sizeof(Derive)<<endl;
- return 0;
- }
结果自然是
12
20
Base类里的int a;char *p;占8个字节。
而虚析构函数virtual ~Base();的指针占4子字节。
其他成员函数不归入sizeof统计。
Derive类首先要具有Base类的部分,也就是占12字节。
int d;char *p;占8字节
static int st;不归入sizeof统计
所以一共是20字节。
在考虑在Derive里加一个成员char c;
- class Derive:public Base
- {
- public:
- Derive():Base(){};
- ~Derive(){};
- private:
- static int st;
- int d;
- char *p;
- char c;
- };
这个时候,结果就变成了
12
24
一个char c;增加了4字节,说明类的大小也遵守类似class字节对齐,的补齐规则。
具体的可以看我那篇《5分钟搞定字节对齐》
至此,我们可以归纳以下几个原则:
1.类的大小为类的非静态成员数据的类型大小之和,也 就是说静态成员数据不作考虑。
2.普通成员函数与sizeof无关。
3.虚函数由于要维护在虚函数表,所以要占据一个指针大小,也就是4字节。
4.类的总大小也遵守类似class字节对齐的,调整规则。