内存对齐
内存对齐的原因
- 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
对齐规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
- 数据成员对齐规则: 类(class)、结构体(struct)或联合体(union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
- 整体对齐规则: 在数据成员完成各自对齐之后,类、结构体或联合体本身也要进行对齐,对齐将按照#pragma pack指定的数值和类、结构体或联合体最大数据成员长度中,比较小的那个进行。
结合1、2可推断,当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
成员变量
测试一
测试代码:
#include <iostream>
using namespace std;
class A {
};
class B {
public:
short a;
};
class C {
public:
short a;
int b;
};
int main() {
cout << "A:" << sizeof(A) << endl;
cout << "B:" << sizeof(B) << endl;
cout << "C:" << sizeof(C) << endl;
return 0;
}
测试结果:
A:1
B:2
C:8
当为空类时,编译器会给空类分配1个字节的内存空间,这样的话,创建的实例所指向的就是有意义的内存空间。
对比类B和C,B的成员变量为short型,占2个字节,C的第一个成员变量存放在offset为0的地方,第二个成员变量占4字节,故编译器插入2个字节凑足4的整数倍,使得第二个成员变量存放在offset为4的地方。
测试二
测试代码:
#include <iostream>
using namespace std;
class A1{};
class A2{};
class B1:public A1{
public:
A1 a1;
char ca;
};
class B2:public A1{
public:
A2 a2;
char ca;
};
class B3:public A1{
public:
char ca;
A1 a1;
};
int main() {
cout<<sizeof(B1)<<endl;
cout<<sizeof(B2)<<endl;
cout<<sizeof(B3)<<endl;
return 0;
}
测试结果:
3
2
2
如果基类没有成员,标准允许派生类的第一个成员与基类共享地址,基类并没有占据任何实际的空间,但此时若该派生类的第一个成员类型仍然是基类,编译器仍会为基类分配1字节的空间,这是因为C++标准要求类型相同的对象必须地址不同。
测试三
测试代码:
#include <iostream>
using namespace std;
class A {
public:
short a;
int b;
double c;
};
class B {
public:
short a;
double b;
int c;
};
int main() {
A classA;
B classB;
cout << "classA:" << sizeof(classA) << endl;
cout << "classA.a:" << &classA.a << endl;
cout << "classA.b:" << &classA.b << endl;
cout << "classA.c:" << &classA.c << endl;
cout << "classB:" << sizeof(classB) << endl;
cout << "classB.a:" << &classB.a << endl;
cout << "classB.b:" << &classB.b << endl;
cout << "classB.c:" << &classB.c << endl;
return 0;
}
测试结果:
classA:16
classA.a:0x28ff20
classA.b:0x28ff24
classA.c:0x28ff28
classB:24
classB.a:0x28ff08
classB.b:0x28ff10
classB.c:0x28ff18
类A的第一个成员变量存放在0x28ff20~0x28ff21,第二个成员变量占4字节,offset为4,在0x28ff24~0x28ff27,第三个成员变量占8字节,offset为8,在0x28ff28~0x28ff2f,故类A占16字节。
类B的第一个成员变量存放在0x28ff08~0x28ff09,第二个成员变量占8字节,offset为8,在0x28ff10~0x28ff17,第三个成员变量占4字节,offset取4的整数倍为16,在0x28ff18~0x28ff1b,由于类B本身也要对齐,这里最大成员变量长度为8,故类B的大小需为8的整数倍,编译器在最后插入4个字节,共占24字节。
static关键字
测试代码:
#include <iostream>
using namespace std;
class A {
public:
static short a;
int b;
double c;
};
short A::a = 0;
int main() {
A classA;
cout << "A:" << sizeof(A) << endl;
cout << "A::a:" << &A::a << endl;
cout << "classA:" << sizeof(classA) << endl;
cout << "classA.a:" << &classA.a << endl;
cout << "classA.b:" << &classA.b << endl;
cout << "classA.c:" << &classA.c << endl;
return 0;
}
测试结果:
A:16
A::a:0x407020
classA:16
classA.a:0x407020
classA.b:0x28ff20
classA.c:0x28ff28
类A为16字节,这是由于在该测试代码中,其静态成员变量存储在全局数据区,独立于类的实例,为所有该类实例所共享。
#pragma pack字节对齐
测试代码:
#include <iostream>
using namespace std;
#pragma pack(1)
class A {
public:
char a;
short b;
double c;
};
#pragma pack()
#pragma pack(4)
class B1 {
public:
char a;
short b;
double c;
};
#pragma pack()
#pragma pack(4)
class B2 {
<