对于一个ARM系统来说,32位数据如int的访问必须是4字节对齐的,16位就要2字节对齐(struct 结构中都是int16_t,int32_t可不需要添加__packed,如果有偶数个char型也可以不添加,因为可以按半字对齐,如str中有5个char型,不加则为6,加则为5,但也破坏了边界对齐的形态,会增加读取效率),否则就会有Data-Abort的异常。因此在一个struct当中,并不能保证如果按照正常顺序写下来,这些变量就一定是对齐的。例如struct a { char a; int b;};那么b如果紧跟着a,就不会是4字节对齐的。编译就对于这些没有对齐的重新安排他们的顺序使得他们按要求对齐。
5.5.4. __packed 结构与单个 __packed 字段
优化已压缩的 struct 时,编译器尝试推算每个字段的对齐以改善访问。 但是,编译器并非总是可以推算 __packed
struct 中每个字段的对齐。 与之相对的是,将 struct 中的单个字段声明为 __packed
时,可以确保对 struct 中自然对齐成员的快速访问。 因此,需要使用压缩结构时,建议您始终压缩结构中的单个字段,而不是整个结构本身。
Note
将 struct 的单个未对齐字段声明为 __packed
还具有其他的优点:程序员可以更清楚的看出 struct 的哪些字段没有对齐。
有关不压缩 struct、压缩整个 struct 和压缩 struct 的单个字段三者之间的差别,将使用Table 5.10 中显示的三个 struct 的执行来加以说明。
在第一个执行中,struct 没有压缩。 在第二个执行中,将整个结构 mystruct
限定为 __packed
。 在第三个执行中,从 mystruct
结构中删除了__packed
属性,并且将单个未对齐字段声明为 __packed
。
Table 5.11 是编译器为Table 5.10 中的每个示例实现生成的机器代码的反汇编,其中,每个实现的 C 代码均使用选项 -O2
进行了编译。
Note
-Ospace
和 -Otime
编译器选项控制对未对齐元素的访问是内联进行还是通过函数调用。 使用 -Otime
将导致内联未对齐访问,而使用 -Ospace
则导致调用函数来进行未对齐访问。
在Table 5.11 的未压缩 struct 的反汇编中,编译器始终访问位于对齐字或半字地址上的数据。 编译器可以执行此操作是因为填充了 struct,因此 struct 的每个成员均位于自然大小边界上。
在Table 5.11 中的 __packed
struct 反汇编中,缺省情况下,字段 one
和 three
在其自然大小边界上对齐,因此编译器可以进行对齐访问。 对可以将其标识为对齐的字段,编译器始终执行对齐字或半字的访问。 对于未对齐字段 two
,编译器使用多重对齐内存访问 (LDR
/STR
/LDM
/STM
),与固定移位和屏蔽组合在一起,用于访问内存中的正确字节。 编译器调用 AEABI 运行时例程 __aeabi_uread4
以读取未知对齐的无符号字,从而访问字段 four
,原因为无法确定该字段是否位于其自然大小边界上。
在Table 5.11 中具有单独压缩字段的 struct 的反汇编中,对字段 one
、two
和 three
的访问方式,与在将整个 struct 限定为 __packed
时的访问方式相同。 但是,与压缩整个 struct 的情况相反,编译器对字段 four
进行字对齐访问,因为结构中的 __packed short
的存在可以帮助编译器确定字段 four
位于其自然大小边界上。