什么叫做字节对齐?
对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。
对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
- 数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
- 联合 :按其包含的长度最大的数据类型对齐。
- 结构体: 结构体中每个数据类型都要对齐。
编译器是按照什么样的原则进行对齐的?
- 数据类型自身的对齐值:
对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,类型,其自身对齐值为4,double类型,其对齐值为8,单位字节。
- 结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
- 指定对齐值:#pragma pack (value)时的指定对齐值value。
- 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
有效对齐值N是最终用来决定数据存放地址方式的值,最重要。
有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。
第一个数据变量的起始地址就是数据结构的起始地址。
结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整
举例说明:
struct B
{
char b;
int a;
short c;
};
编译器默认情况下会对这个struct作自然边界对齐。
假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。
第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.
第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。
第三个变量c,自身对齐值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。
再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。
故B从0x0000到0x000B共有12个字节,sizeof(struct B)=12;
例子:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=0;
第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。
第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C只占用0x0000到0x0007的八个字节。
所以sizeof(struct C)=8.
案例分析
例子:联合体
union u
{
double a;
int b;
};
union u2
{
char a[13];
int b;
};
union u3
{
char a[13];
char b;
};
cout<<sizeof(u)<<endl; // 8cout<<sizeof(u2)<<endl; // 16cout<<sizeof(u3)<<endl; // 13
联合体按其包含的长度最大的数据类型对齐。联合体的内存不会为了所有成员安排,而是只取最大的成员的所需内存大小,每次只能使用其中一个成员。
联合体的内存除了取最大成员内存外,还要保证是所有成员类型size的最小公倍数
u中 a的数据类型为double,长度为8,有效对齐值为8,b的有效对齐值是4,而选择按8对齐。复核是8的整数倍。
u2中,a占据13个字节,包含长度最大,但char的有效对齐值是1.int的有效对齐值是4,所以复核13%4 !=0 还要补3个字节。所以共16个子节大小。
u3中,a占据13个字节,包含长度最大,a和b的有效对齐值都是1 复核13%1===0
所以以上分别是 8 16 13
例子:结构体
struct s1
{
char a;
double b;
int c;
char d;
};
struct s2
{
char a;
char b;
int c;
double d;
};
cout<<sizeof(s1)<<endl; // 24cout<<sizeof(s2)<<endl; // 16
s1:
a的有效对齐值是1,占据一个字节
b的有效对齐值是8,前面补7个字节,占据8个字节。
c的有效对齐值是4,
D的有效对齐值是1
复核 1+7+8+4+1=21 21%8 == 5;
所以最后要补3个字节。最终结果是24;
s2:
a的有效对齐值是1,占据1个字节
b的有效对齐值是1,占据1个字节
c的有效对齐值是4,前面需要补2个字节。占据4个字节。
d的有效对齐值是8,占据8个字节
1+1+2+4+8=16
复核16%8==0
所以结果是16.
由此可见,结构体中,相同的数据类型,不同的顺序结果也会不一样。我们通常会把长度多的放在最后。通常会减少内存占用。
网友评论