转自:http://www.cnblogs.com/longlybits/articles/2385343.html (有改动)
内存对齐
在用sizeof运算符求某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列,而不是简单地顺序排列,这就是内存对齐。
内存对齐的原因:
1)某些平台只能在特定的地址处访问特定类型的数据;
2)提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。
对齐策略
使用下面这条指令可以查看当前的 packing alignment(默认的对齐参数)
#pragma pack(show)
数据对齐的规则:“在自身对齐参数 和 指定对齐参数 中,选择小的对齐”
- 自身对齐参数:
1) 数据成员:数据成员的类型所占字节的长度
2) 复杂数据整体:(如结构体)其最大的数据成员的长度N;偏移量offset须满足:offset%N = 0
- 指定对齐参数:
预编译指令 #pragma pack(…)
1) 作用:指定结构体、联合以及类成员的 packing alignment;(默认的对齐参数)
2) 语法:#pragma pack( [show] , [push | pop] , [ identifier] , n )
3) 说明:pack 提供数据声明级别的控制,对定义不起作用;调用pack时不指定参数,n将被设成默认值;
4) 具体分析:
1、show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示;
2、push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;
3、pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;
4、identifier:可选参数;同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;
5、n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。
默认情况
win32下默认的对齐参数为8,win64下默认的对齐参数为16(即 double 所占字节数)
所以当没有特别指定对齐参数的时候,按自身对齐参数来对齐数据:
1)编译器在为结构体变量开辟空间时,首先找到结构体中最宽的数据类型,然后寻找内存地址能被该数据类型大小整除的位置,这个位置作为结构体变量的首地址。所以将最宽数据类型的大小作为对齐参数。
2)结构体每个成员相对结构体首地址的偏移量(offset)必须是对齐参数的整数倍(offset%N = 0),如有需要会在成员之间填充字节。也可能会在最后一个成员末尾填充若干字节使得所占空间大小是最宽数据类型大小的整数倍。
栗子
下面看一下sizeof在计算结构体大小的时候具体是怎样计算的
1.test1 空结构体
typedef struct node { }S;
sizeof(S)=1 或 sizeof(S)=0 //在C++中占1字节,而在C中占0字节。
2.test2
typedef struct node1 { int a; char b; short c; }S1;
sizeof(S1)=8 //因为结构体node1中最长的数据类型是int,占4个字节,因此以4字节对齐,则存储方式为:
|--------int--------| 4字节
|char|----|--short-| 4字节
3.test3
typedef struct node2 { char a; int b; short c; }S2;
siezof(S3)=12 //最长数据类型为int,占4个字节。因此以4字节对齐,则存储方式为:
|char|----|----|----| 4字节
|--------int--------| 4字节
|--short--|----|----| 4字节
4.test4 含有静态数据成员
typedef struct node3 { int a; short b; static int c; }S3;
sizeof(S3)=8 //这里结构体中包含静态数据成员,而静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的)。则存储方式为:
|--------int--------| 4字节
|--short-|----|----| 4字节
而变量c是单独存放在静态数据区的,因此用siezof计算其大小时没有将c所占的空间计算进来。
5.test5 结构体中含有结构体
typedef struct node4 { bool a; S1 s1; short b; }S4;
sizeof(S4)=16 //s1占8字节,而s1中最长数据类型为int,占4个字节,bool类型1个字节,short占2字节,因此以4字节对齐,则存储方式为:
|-------bool--------| 4字节
|-------s1----------| 8字节
|-------short-------| 4字节
6.test6
typedef struct node5 { bool a; S1 s1; double b; int c; }S5;
sizeof(S5)=32 //s1占8个字节,而s1中最长数据类型为int,占4个字节,而double占8个字节,因此以8字节对齐,则存放方式为:
|--------bool--------| 8字节
|---------s1---------| 8字节
|--------double------| 8字节
|----int----|---------| 8字节
7.test7
若在程序中使用了 #pragma pack(n) 强制以n字节对齐时,默认情况下n为8.
则比较n和结构体中最长数据类型所占的字节大小,取两者中小的一个作为对齐标准。
若需取消强制对齐方式,则可用命令#pragma pack(),即恢复到默认
如果在程序开头使用命令#pragma pack(4),对于下面的结构体
typedef struct node5 { bool a; S1 s1; double b; int c; }S5;
sizeof(S5)=24 //强制以4字节对齐,而S5中最长数据类型为double,占8字节,因此以4字节对齐。在内存中存放方式为:
|-----------a--------| 4字节
|--------s1----------| 4字节
|--------s1----------| 4字节
|--------b-----------| 4字节
|--------b-----------| 4字节
|---------c----------| 4字节
总结
总结一下,在计算sizeof时主要注意一下几点:
1)若为空结构体,则只占1个字节的单元
2)若结构体中所有数据类型都相同,则其所占空间为 成员数据类型长度×成员个数
若结构体中数据类型不同,则取最长数据类型成员所占的空间为对齐标准,数据成员包含另一个结构体变量t的话,则取t中最 长数据类型与其他数据成员比较,取最长的作为对齐标准,但是t存放时看做一个单位存放,只需看其他成员即可。
3)若使用了#pragma pack(n)命令强制对齐标准,则取n和结构体中最长数据类型占的字节数两者之中的小者作为对齐标准。
另外除了结构体中存在对齐之外,普通的变量存储也存在字节对齐的情况,即自身对齐。编译器规定:普通变量的存储首地址必须能被该变量的数据类型宽度整除。