4.5 紧凑数据空间分配
通过将数据打包到结构体中,可以减少应用程序所需的内存。这在嵌入式系统中尤其重要,尤其是当你需要存储和访问大型数据数组时。
如果结构体中的各个成员没有打包,编译器可能会根据每个成员的自然对齐方式,在结构体内部添加填充字节,以加快对单个成员的访问速度。
kf32clang提供了 pragma 和属性,用于在结构体或联合体中打包成员,而不添加任何填充字节。
表4.5 在结构体或联合体中打包成员
| Pragma或 attribute | 描述 |
|---|---|
#pragma pack (n) | 对于每个成员,如果 n 字节小于该成员的自然对齐方式,则将对齐方式设置为 n 字节;否则,使用该成员的自然对齐方式。 |
__attribute__((packed)) | 这等同于 #pragma pack(1)。不过,该属性也可以用于结构体或联合体中的单个成员。 |
4.5.1 打包整个结构体
要打包整个结构体或联合体,可以在结构体声明中使用 __attribute__((packed)) 或 #pragma pack(n),如代码示例所示。该属性和 pragma 会应用于结构体或联合体的所有成员。如果某个成员本身是结构体,则该结构体的对齐方式为 1 字节,但该结构体内部的成员仍保持它们的自然对齐方式。
/* 未打包:结构体大小为 12 字节,对齐方式为 4 字节 */
struct stc
{
char one;
short two;
char three;
int four;
};
/* 使用 __attribute__((packed)) 打包:结构体大小为 8 字节,对齐方式为 1 字节 */
struct __attribute__((packed)) stc
{
char one;
short two;
char three;
int four;
};
使用 #pragma pack(n) 时,结构体的对齐方式为应用 #pragma pack(n) 后结构体中最大成员的对齐方式。
表4.6 打包结构体


上:未打包时结构体的内存布局;下:打包后结构体的内存布局。
4.5.2 在结构体中打包单个成员
要打包结构体的单个成员,可以在该成员上使用 __attribute__((packed))。这会将该成员对齐到字节边界,从而减少整个结构体所需的内存。它不会影响其他成员的对齐方式。因此,整个结构体的对齐方式仍等于最大成员在未使用 __attribute__((packed)) 时的自然对齐方式。
/* 仅打包成员 four:结构体大小为 10 字节,对齐方式为 2 字节 */
struct stc
{
char one;
short two;
char three;
int __attribute__((packed)) four;
};
表4.7 打包结构体中单个成员


上:未打包单个成员时结构体的内存布局;下:打包单个成员后结构体的内存布局。
4.5.3 访问结构体中被打包的成员
如果结构体或联合体的某个成员被打包,因此不具有其自然对齐方式,那么访问该成员时必须通过包含它的结构体或联合体进行。不能获取该打包成员的地址作为指针使用,因为指针可能未对齐。即使目标平台支持未对齐访问,解引用这样的指针也可能不安全,因为某些指令始终要求地址按字对齐。
struct __attribute__((packed)) foobar
{
char x;
short y;
};
short get_y(struct foobar *s)
{
// Correct usage: the compiler will not use unaligned accesses
// unless they are allowed.
return s->y;
}
short get2_y(struct foobar *s)
{
short *p = &s->y; // Incorrect usage: 'p' might be an unaligned pointer.
return *p; // This might cause an unaligned access.
}