一、概述
- 类所占内存的大小是由成员变量(静态变量除外)决定的,成员函数是不计算在内的。
【原因】:成员函数还是以一般的函数一样的存在。a.fun()是通过fun(a.this)来调用的。所谓成员函数只是在名义上是类里的。其实成员函数的大小不在类的对象里面,同一个类的多个对象共享函数代码。而我们访问类的成员函数是通过类里面的一个指针实现,而这个指针指向的是一个table,table里面记录的各个成员函数的地址( 具体可以看多态函数的实现 )。所以我们访问成员函数是间接获得地址的。所以这样也就增加了一定的时间开销,这也就是为什么我们提倡把一些简短的,调用频率高的函数声明为inline形式(内联函数)【见章节:内联函数】。
二、事例分析 - 类
2.1、空
class Test_null
{
};
- c++要求每个实例在内存中都有独一无二的地址,因此空类也会被实例化。
【原因】:编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了独一无二的地址了。所以空类的sizeof为1。
2.2、仅成员变量
class Test_null
{
int a;
char b;
};
- int 占 4字节,char占一字节,补齐3字节(遵循内存对齐原则)
- static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区
2.3、成员变量 + 成员函数 + 虚函数
class Test_null
{
public:
void funb();
virtual void func() = 0;
private:
char b;
int a;
};
- 成员函数不占空间,
==其实类的成员函数 实际上与 普通的全局函数一样。
只不过编译器在编译的时候,会在成员函数上加一个参数,传入这个对象的指针。==
成员函数地址是全局已知的,对象的内存空间里根本无须保存成员函数地址。
- 所有虚函数均通过vptr指针索引,所以只计算vptr
- 64位操作系统 指针大小为8
2.4、继承 or 菱形继承
- 子类的大小是本身成员变量的大小加上父类的大小,父类子类共享一个虚函数指针
- 菱形继承的话,为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.并采取就近的法则,离哪个最近的倍数,就是该类的大小,即大小为5的话,会调整为 8
三、事例分析 - 结构体
3.1、为什么要存在内存对齐呢?
结构体存在内存对齐,类(对象)也如此。
【原因】:如果不考虑对齐,应该是以下方式存储的:
char A; int B;
因为计算机的字长是4字节(一次处理的二进制数,不同计算机不同)的,所以在处理变量A与B时的过程可能大致为:
- A:将0x00-0x03共32位读入寄存器,再通过左移24位再右移24位运算得到a的值(或与0x000000FF做与运算)
- B:将0x00-0x03这32位读入寄存器,通过位运算得到低24位的值;再将0x04-0x07这32位读入寄存器,通过位运算得到高8位的值;再与最先得到的24位做位运算,才可得到整个32位的值。
- 过程就会非常麻烦,因此牺牲几个字节,来提高效率。
3.2、长度分析
typedef struct T
{
char c; //本身长度1字节
__int64 d; //本身长度8字节
int e; //本身长度4字节
short f; //本身长度2字节
char g; //本身长度1字节
short h; //本身长度2字节
};
可是若申请的是T结构体数组,那么会出现下面的情况:即会出现边界不对称
因此最终的对齐方法为:结构体整体为最大成员的整数倍
最后:sizeof(T)==0x20