跳到主要内容

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.
}