Linux 沿用的对齐策略是,2字节数据类型(例如short
)的地址是2的倍数,而较大的数据类型(例如int
、int*
、float
和double
)的地址必须是4的倍数。
Windows 要求任何 K 字节基本对象的址址必须是 K 的倍数,K = 2, 4 或者 8。特别的,它要求一个double
或者long long
类型数据的地址应该是8的倍数。
对齐准则
四个重要的基本概念:
- 数据类型自身的对齐值:char型数据自身对齐值为1字节,short型数据为2字节,int/float型为4字节,double型为8字节。
- 结构体或类的自身对齐值:其成员中自身对齐值最大的那个值。
- 指定对齐值:#pragma pack (value)时的指定对齐值value。
- 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小者,即有效对齐值=min{自身对齐值,当前指定的pack值}。
其中,有效对齐值N
是最终用来决定数据存放地址方式的值。有效对齐N
表示“对齐在N
上”,即该数据的“存放起始地址 % N = 0”。而数据结构中的数据变量都是按定义的先后顺序存放。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐存放,结构体本身也要根据自身的有效对齐值圆整(即结构体成员变量占用总长度为结构体有效对齐值的整数倍)。
例1:
1 |
|
结果:sizeof(strcut A)
值为8;sizeof(struct B)
的值却是12。
例2:
1 |
|
结果:sizeof(struct C) = 8
。
栈内存对齐
在VC/C++中,栈的对齐方式不受结构体成员对齐选项的影响。总是保持对齐且对齐在4字节边界上。(并未考证64位)
位域对齐
位域成员不能单独被取sizeof
值。下面主要讨论含有位域的结构体的sizeof
。
C99 规定int
、unsigned int
和bool
可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型的存在。位域作为嵌入式系统中非常常见的一种编程工具,优点在于压缩程序的存储空间。
其对齐规则大致为:
- 如果相邻位域字段的类型相同,且其位宽之和小于类型的
sizeof
大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;- 如果相邻位域字段的类型相同,但其位宽之和大于类型的
sizeof
大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;- 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6 采取不压缩方式,Dev-C++ 和 GCC 采取压缩方式;
- 如果位域字段之间穿插着非位域字段,则不进行压缩;
- 整个结构体的总大小为最宽基本类型成员大小的整数倍,而位域则按照其最宽类型字节数对齐。
例3:
1 |
|
位域类型为char
,第1个字节仅能容纳下element1
和element2
,所以element1
和element2
被压缩到第1个字节中,而element3
只能从下一个字节开始。因此sizeof(BitField)
的结果为2。
例4:
1 |
|
位域中最宽类型int
的字节数为4,因此结构体按4字节对齐,在 VC6 中其sizeof
为16。
例5:
1 |
|
长度为0的位域告诉编译器将下一个位域放在一个存储单元的起始位置。如上,编译器会给成员element1
分配3位,接着跳过余下的4位到下一个存储单元,然后给成员element3
分配5位。故上面的结构体大小为2。