在阅读到条款39(明智而审慎地使用private继承)时,对empty class比较困惑,本文主要对empty class进行简单的讨论。首先简单介绍一下c++内存使用机制(参考了其他书籍):
1.程序使用内存区
一个程序占用的内存区一般分为5种:
(1)全局、静态数据区:存储全局变量及静态变量(包括全局静态变量和局部静态变量)
(2)常量数据区:存储程序中的常量字符串等。
(3)代码区:存储程序的代码。
(4)栈:存储自动变量或者局部变量,以及传递的函数参数等。
(5)堆:存储动态产生的数据。
栈:
一个程序使用的栈的大小是固定的,由编译器决定,一般是1MB。栈的内存是系统自动分配的,压栈和出栈都有相应的指令进行操作,所以效率较高。分配的内存空间是连续的,不会产生碎片。
堆:
有开发人员动态分配和回收。在分配内存时,系统需要按照一定的算法在堆空间中寻找合适大小的空闲堆,并修改相应的维护堆空闲空间的链表,然后返回地址给程序。因此效率比栈要低,还容易产生内存碎片。
2.对象的生命周期
(1)全局变量的作用域是整个程序,在程序调用main()函数之前被创建,当程序退出main()函数之后,全局对象才被销毁。静态变量与全局对象类似,虽然作用域不是整个程序,但静态变量是存储在全局/静态数据区中,在程序开始时已经分配好。因此声明为静态变量的对象第一次进入作用域时被创建,直到程序退出时被销毁。
(2)通过new创建的对象,一直存在于内存中,直到被delete销毁,容易产生内存泄漏(运行过程中不停的有申请内存,长时间运行后容易导致内存不足导致奔溃)。当程序运行结束后,系统才会回收。
(3)对于中间临时变量一般是通过copy constructor创建的。在实际开发中,通过值传递传递参数,重载+及++等操作符,对对象进行算术运算时,也会有临时对象,对于这些情况,都要尽量避免不必要的临时对象的出现。它因为们不容易被妨开发人员发现,对于那些占用内存较多,创建速度较慢的对象,常常是造成程序性能下降的瓶颈。
3.C++对象的内存布局
(1)非静态数据成员是影响对象占据内存大小的主要因素,随着对象数目的增加,非静态数据成员占据的内存会相应增加。
(2)所有的对象共享一份静态数据成员,所以静态数据成员占据的内存的数量不会随着对象数目的增加而增加。
(3)静态成员函数和非静态成员函数不会影响对象内存的大小,虽然其实现会占据相应的C++内存使用机制空间,同样也不会随着对象数目的增加而增加。
(4)如果对象中包含虚函数,会增加4个字节的空间(虚函数表指针),不论有多少个虚函数。
4.字节对齐
在处理内存时,系统会自动将内存对齐,这样虽然会浪费一些内存,但由于CPU在对齐方式下运行比较快,所以一般都是对程序性能还是有好处的。
计算机为了快速的读写数据,默认情况下将数据存放在某个地址的起始位置,如:整型数据(int)默认存储在地址能被4整除的起始位置,字符型数据(char)可以存放在任何地址位置(被1整除),短整型(short)数据存储在地址能被2整除的起始位置。这就是默认字节对齐方式。
计算一个结构体长度的基本思路:
(1)先计算出各个成员长度;
(2)找出最大长度Max,结构体长度一般是该成员的整数倍(对于如果成员是其他结构体得特殊考虑);
(3)并按照最大成员长度出现的位置将结构体分为若干部分;
(4)再对各部分进行分析......(网上说的简便算法:各个部分长度一次相加,求出大于该和的最小M的整数倍即为该部分长度)
(5)将各个部分长度相加之和即为结构体长度
空结构、空类:class base {}; // sizeof(base) == 1 编译器会为其分配一个字节的空间。
static:存放在全局数据区,而sizeof计算栈中分配的空间大小,所以不计算在内。
enum、non-virtual functio、typedef:都不会占用空间。
例子:
struct student
{
char name[5]; // 5 bytes
int num; // 4 bytes
short score; // 2 bytes
static int times;// 0 byte
};
int student::times = 0;
本来只用了11bytes = 5 +4 +2 bytes. 但是由于int是4字节,存放在地址能被4整除的起始位置,所以也不是简单的5*3=15bytes,而是sizeof(student)=16bytes。
数据对齐图:
|char|char|char|char|
|char|----|----|----|
|--------int--------|
|--short--|----|----|
修改顺序:
struct student
{
int num; // 4 bytes
char name[5]; // 5 bytes
short score; // 2 bytes
static int times;// 0 byte
};
int student::times = 0;
数据对齐图:
|--------int--------|
|char|char|char|char|
|char|----|--short--|
sizeof(student)=12bytes
其他例子:
struct test1
{
int a;
int b[4];
};
sizeof(test1)=sizeof(int)+4*sizeof(int)=4+4*4=20;
struct test2
{
char a; // 1
int b; // 4
double c;// 8(max is 8)
bool d; // 1
};
sizeof(test2) = 8::<1+4> + 8 + 8::<1> = 24;
struct test3
{
char a; // 1
test2 b;// 24(max is 8)
int c; // 1
}
sizeof(test3) = 8::<1> + 24::<test3::double> + 8= 40;
struct test4
{
char a; // 1
int b; // 4
}; // 4(1) + 4 = 8
struct test5
{
char c; // 1
test4 d; // 8
double e;// 8(max is 8)
bool f; // 1
};
sizeof(test5) = 8::<1> + 8::<test4> + 8(double) + 8::<1>= 32;//之前算成40了,是我算错了,感谢u010537526的提醒
对于union:
union的长度取决于其中的长度最大的那个成员变量的长度。即union中成员变量是重叠摆放的,其开始地址相同。
1. 一般而言,共用体类型实际占用存储空间为其最长的成员所占的存储空间;
2. 若是该最长的存储空间对其他成员的元类型(如果是数组,取其类型的数据长度,例int a[5]为4)不满足整除关系,该最大空间自动延伸;
比如:
union DemoUnion
{
char a; // 1
int b[5];// 4 * 5
double c;// 8
int d[3];// 4 * 3
};
原本计算空间的方法是:因为int b[5]最长,所以sizeof[DemoUnion] = sizeof(int) * 5 = 20。
但是如果只是20个单元的话,那可以存几个double型(8位)呢?两个半?当然不可以,所以mm的空间延伸为既要大于20,又要满足其他成员所需空间的整数倍,即24
所以union的存储空间先看它的成员中哪个占的空间最大,拿他与其他成员的元长度比较,如果可以整除就行。
4.空类
#include <iostream>
using namespace std;
// empty class
class base {};
// Derived empty class
class base0 : public base
{
int a;
};
// empty class + enum + static
class base1
{
enum type{red,green};
static int sValue;
};
int base1::sValue = 0;
// non-empty class + virtual
class base2
{
public:
virtual void printError() = 0;
};
// derived non-empty class + virtual
class base3 : public base2
{
public:
virtual void printError() {}
};
// derived non-empty class + virtual + others
class base4 : public base2
{
public:
virtual void printError() {}
private:
int i;
double d;
bool b;
};
// derived non-empty class + virtual + others
class base5 : public base2
{
private:
int i;
double d;
bool b;
public:
virtual void printError() {}
};
// virtual derived non-empty class + virtual + others
class base6 : virtual public base2 //虚继承
{
public:
virtual void printError() {}
};
int main()
{
cout << "base0 : " << sizeof(base0) << endl;
cout << "base1 : " << sizeof(base1) << endl;
cout << "base2 : " << sizeof(base2) << endl;
cout << "base3 : " << sizeof(base3) << endl;
cout << "base4 : " << sizeof(base4) << endl;
cout << "base5 : " << sizeof(base5) << endl;
cout << "base6 : " << sizeof(base6) << endl;
cout << sizeof(char *) << endl;
return 0;
}
运行结果:
base0 : 4
base1 : 1
base2 : 4
base3 : 4
base4 : 24
base5 : 24
base6 : 8
4
Press any key to continue
一旦添加了虚函数,会产生一个指针vptr指向虚函数表vtable。虚承继中:由于涉及到虚函数表和虚基表,会同时增加一个(多重虚继承下对应多个)vfPtr指针指向虚函数表vfTable和一个vbPtr指针指向虚基表vbTable,这两者所占的空间大小为:8(或8乘以多继承时父类的个数);32位系统,指针的大小为4字节。所以才会有上面的结果。