C++确实是一门很深奥很底层的语言…没想到就连字节对齐也有这么多门道。希望通过本文全面记录并帮助读者理解字节对齐。我觉得计算机知识的学习不仅要知其然,更要知其所以然,知道为什么要这样设计,而不是死记硬背,才能领悟前任这样设计的思想,未来真正运用的时候才能融会贯通
计算机在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
在讲解如何进行字节对齐前,先介绍对齐模数的概念
ps:在我的64位OS上对齐模数是8,我猜测对齐模数可能与电脑的位数有关
先从结构体开始讲解,类的字节对齐规则实际上也是基于结构体的字节对齐规则。
提示:以下例子的运行结果是在64位GCC编译器下得到的,在这一情况下每种基本类型占用的字节数位:char 1 short 2 int 4 float 4 double 8 指针 8
不管是结构体还是类,内存对齐都遵循以下三个原则:
字节对齐的过程:
下面给出一个实例:
//我的电脑是64位机器,默认的对齐模数是8字节
struct student_info {
char name;//偏移量为0,满足对齐方式,name占用一个字节
int age;//偏移量为1,不是 sizeof(int)的倍数,需要补足3个字节,age存放在偏移量为4的地址上,它自己占用4个字节
int number;//偏移量为4,是sizeof(int)的倍数,不需要补足字节,number存放在偏移量为8的地址上,它自己占用4个字节
char add;//偏移量为12,是sizeif(char)的倍数,不需要补足字节,add存放在偏移量为12的地址上,它自己占用1个字节
double test;//偏移量为13,不是sizeof(double)的倍数,需要补足7个字节,补足后test存放在偏移量为16的地址上,它自己占用8个字节
char tttt;//此时偏移量为24,是sizeof(char)的倍数,不需要补足字节,tttt存放在偏移量为24的地址上,它自己占用1个字节
};//,所有成员变量都分配了空间,空间的总大小为25,不是实际对齐模数的倍数,需要填充字节,填充至32
int main() {
cout << sizeof(student_info) << endl;
return 0;
}
//输出32
假如上面的例子指定对齐模数为2
#pragma pack(2)
答案会是22
(这部分参考了别人的博客)
内嵌结构体的第一个成员编程在外结构体中的偏移量,是“MIN(指定对齐模数,内嵌结构体中最大数据类型)”的倍数。
例子:
struct TEST
{
int a;
char b;
char c;
int d;
char e;
struct child {
int f;
long long g; //8 bytes,long long 是chile结构体的最大数据类型
}ch;
char ci;
};
// sizeof(struct TEST) = 40
在这个例子中,前面a,b,c,d,e的计算规则与普通结构体的计算规则相同,字符e的起始位置为12,占一个字节,然而由于child这个内嵌结构体的偏移量需要是min(最长数据成员所占字节 long long 8,对齐模数8) = 8的整数倍,因此需要在char e后进行填充,填充到16,然后再存,接下来的过程如下
需要首先类中的字节对齐规则比较复杂,涉及虚函数、静态成员、虚继承、多继承、空类等情况。类本身是没有大小可言的,这里计算的sizeof(xxx)实际上是该类所对应对象的大小
具体规则总结如下:
字节对齐看似简单,但是深究起来其实是对很多知识点的总结,包括