今天偶然在贴吧里面看到有人问
struct A
{
char a;
double b;
};
使用C++的
sizeof(A);
结果为:16。
已经周折才把这个搞明白了。
下面我来说一下,看你能看的懂吗:
从大的方面来讲,今天我们说的这个内存边界对准,其实是为了提高内存访问速率,同时也提高了程序的可移植性。
我主要说明提高内存访问速率。
首先介绍一个事实,如果我们语言里面的内置数据类型所声明的对象没有按照其相应的内存边界对准,那么存储器很有可能要多次访存。
为什么?
假设有这么一种处理器,它只能访问8的倍数的内存地址(实际上现在的cpu基本都是这样,但不是8倍),而且每次写入或者读取都是8个字节。这么一来对我们的编程就造成影响了。假如有个double b;b的地址假设是0006,那么b所占据的地址段就0006-000D(16进制)。那么好了,这个cpu是不能在一次访问存储器就能取得这个数据的。它需要先访问0地址,取出0006-0007的数据,然后再访问0008-000D的数据。
但是要是我们本来就把double b;放在一个8倍的地址上,那么cpu就可以一次访问存储器就可以取得这个数据了。
所以结论是:内存地址对齐是很有必要的,速度提高了一倍哦。
移植性是因为有的cpu架构对某些类型只能够从特定形式的内存地址存取,否则就会出错。所以对于这类平台必须使用内存边界对准,而其他平台没有这类问题,所以按地址边界做的比较好,对一般机器可以,对别的机器也可以,所以提高了移植性。
接着我们在讲究竟是怎么对齐。我讲讲结构体的内存对齐问题:
假如:
struct A
{
char a;
short b;
int b;
};
那么sizeof(A),究竟是多少呢?
答案是:8
按照一般计算式:1+2+4=7
但是内存边界对准有3条规则:
首先一般编译器自身就有一个默认的内存对齐模数(内存对齐模数就是以几倍地址对齐,模数就是几)是4或者8。VC应该是4
规则1:结构体内部数据的对齐,拿结构体的数据成员的大小(sizeof值)和默认对齐模数,谁小,就以谁为模数。
规则2:结构体本身的内存对齐,拿结构体内的数据成员的大小中最大的和默认对齐模数比较,谁小,就以谁为模数。
规则3:其实也是个定理,如果默认对齐模数比结构体中最大数据成员大是,那么默认对齐模数没用。
模数到底有什么用呢?看下面的实例:
用这个规则我们来看一下上面的struct A。假设默认对齐模式为4
struct A
{
char a;//大小为1,因为1<4,所以以1为对齐模数,意思就是这个数据成员的地址只要是1的倍数就可以了,那么也就是任意地址。
//那我们假设这个结构体的其实地址是0,那么a的地址是0。 即a=[0]
short b;//大小为2,因为2<4,所以以2为对齐模数,意思就是这个数据成员的地址只要是2的倍数就可以了,那么也就是必须放在
//0,2,4,8为尾数的地址上,前面的a地址是0,大小是1,那么这样,后面的变量按理是在1地址上,但是刚才也说了必须要在
//0,2,4,8为尾数的地址上,所以b必须放在2地址上。 即b=[2,3]
int c;//大小为4,因为4=4,那么以4为对齐模数,所以c的地址必须尾数是4的倍数,正好可以放在地址4上。
// 即c=[4,5,6,7]
};
所以结构体A的数据成员的总大小是8。
但并没有完,虽然结果是8。
实际上编译器还要执行规则二。
那就是:用数据结构的最大数据成员大小MAX(char,short,int)=4,和默认的对齐模数4比较,选择较小的那个,所以以4为模数。
结构体的大小 =为了使 ( 数据结构成员的总大小 / 对齐模数 )圆整,而调节数据结构成员的大小后的值。
圆整的意思是: 3 / 4 结果是0.75,对结果圆整是1,调整3变成4,那么结果就是整数了。
所以结构体的大小=(8/4)圆整调后后的结果=8
但并未总是8.
如结构体A
{
double a;
char b;
}
数据成员大小是9
但结构体的大小=12。就是因为圆整。
但为什么要调整结构体的大小呢?(我想)
原因应该是就是满足对齐要求,时候后面变量分配的地址能满足对齐模数的要求。
如果结构体大小为9,那么后面结构体的变量开始地址从9开始,就很麻烦了。