在C语言中我们定义的结构体的大小常常不是所有成员变量大小的总和(大于或者等于),这是因为编译器“暗中”执行了“对齐”的动作,使得访问更有效率,当然也会牺牲一定的空间。
对于8位机来说,内部总线的宽度是8位的,所以无论是存取8位的数据,还是16或32位数据,从什么地址开始存取都是无关紧要的,因为所有的访问最终都会拆分成单个字节访问,因此在8位机上,结构体的大小就等于所有成员大小的累加和。
对于32位机来说,内部总线的宽度是32位的,所以很多时候访问如果按照4字节对齐,那么是最有效率的一种访问方式。有的平台甚至是不允许在非对齐的地址上存取数据。举个例子,访问一个int的变量,如果放在可以被4整除的地址上,那么只需要一个CLK就可以存取,如果放在非对齐的地址上(例如奇数地址),则需要2个CLK分别存取高低两部分再进行拼凑,显然效率低下。
因为8位机上基本没有对齐的问题,所以我们下面的讨论主要针对32位机。
首先来看看对齐的概念:
如果一段长度为n字节的数据所存放的地址m能被n整除, 那么d就是对齐的. 可见, 数据的对齐和自身长度和地址有关. 在32位机器上, 并不是要求每个数据以4字节对齐. 对于short类型的数据来说, 将他们存放在偶数地址上, 就是对齐的. 如果是奇数地址则是非对齐. 对于int类型的数据来说, 必须要将他们存放在能被4整除的地址上, 才是对齐的. 而对于char类型的数据. 任意一个地址都可以被看作是对齐的.
我们在设计结构的体的时候必须要考虑到4种对齐.
第一,结构体成员自身对齐(因为存在单独访问结构体成员的情况. 所以这些成员必须满足上面所说的数据对齐).
约束:结构体每个成员相对于结构体首地址的偏移量(offset)必须是成员自身大小的整数倍. 否则需要在成员之间加入填充字节(internal padding).
第二,结构体自身对齐(因为当存在结构体数组的情况,所以整个结构体也必须满足对齐).
约束:结构体的总大小为结构体最宽基本类型成员大小的整数倍, 如有需要编译器会在最末一个成员之后加上填充字节(trailing padding).
第三,指定对齐(默认对齐),跟编译器还有平台有关,在没有使用编译器关键字如#pragma pack等指定之前,使用是默认对齐。在32位的ARM平台上往往默认对齐是4,也可以利用关键字修改为2,或者1。
第四,有效对齐,会影响成员和结构体最终的对齐方式,有效对齐是自身对齐与指定对齐之间较小的那一个
下面在32位机,默认4字节对齐的环境下的看几个例子.
struct student
{
u8 num;
u8 name;
u16 score;
};
sizeof(student) == 4
struct student
{
u8 num;
u8 name;
u16 sex;
u16 score;
};
sizeof(student) == 6
struct student
{
u8 num;
u8 name;
u8 sex;
u16 score; // 为了保证成员自身是对齐的,需要在之前插入一个字节的padding.
};
sizeof(student) == 6
struct student
{
u8 num;
u8 name;
u32 sex; // 为了保证成员自身是对齐的, 需要在之前插入两个字节的padding.
};
sizeof(student) == 8
struct student
{
u8 num;
u8 name;
u32 sex; // 为了保证成员自身是对齐的, 需要在之前插入两个字节的padding.
u16 score; // 为了保证结构自身是对齐的(按最大的元素u32), 需要在后面加上两个字节的padding
};
sizeof(student) == 12
struct student
{
u8 num;
u8 name;
u32 sex; // 为了保证成员自身是对齐的, 需要在之前插入两个字节的padding.
u16 score;
u8 id; // 为了保证结构自身是对齐的(按最大的元素u32), 需要在后面加上一个字节的padding
};
sizeof(student) == 12
以上的例子所有自身对齐都小于或等于指定对齐(默认对齐)4,因此都是以自身对齐作为有效对齐来考虑的,如果通过编译器将指定对齐修改位2字节对齐,大小和padding会有所不同
#pragma pack (2)
struct student
{
u8 num;
u32 sex; // 成员自身对齐是4,指定对齐是2,所以有效对齐也是2,只需插入一个字节padding.
u16 score; // 结构体自身对齐是4,指定对齐是2,所以有效对齐也是2,8%2==0,已经是对齐的。
};
#pragma pack()
sizeof (student) == 8
可见结构体的大小会受到结构体成员顺序的影响. 如果对结构体占的空间比较敏感. 应该按大小顺序来排列成员(先小后大,或先大后小).
最典型的例子: