在 C 语言中使用结体构对齐和填充
本文将介绍几种在 C 语言中使用结构体来对齐和填充的方法。
了解 C 语言中对齐和填充的基础知识
内存中的所有对象都以主要数据类型表示,如:char
、short
、int
、long
、pointer
等。char
、short
、int
、long
、pointer
等。这些数据类型在内存中都有其相应的大小。在大多数当代 64 位桌面处理器上,char
的大小是 1 个字节,short
是 2 个字节,int
是 4 个字节,pointer
是 8 个字节,以此类推。请注意,这些都不是保证大小(除了 char
),但可以使用 sizeof
操作符检索对象的大小。现在,对齐是编译器用来放置内存中的变量的方法,意味着每个基本数据类型都存储在被相应大小除以的地址上。
通常,利用对齐来更快、更有效地访问数据对象。对齐迫使连续声明的不同数据类型在其地址之间包含一些间距。也就是说,如果我们声明一个结构 st1
,其中有一个指针和一个 char
,如下面的例子所示,它总共会占用 16 个字节。不过要注意,一个指针需要 8 个字节,一个 char
需要一个字节,所以人们会认为 st1
结构一定会占用 9 个字节。但它的表现就像所有成员都是按照最大成员的大小(即 8 个字节)对齐的。st2
结构展示了一个类似的结构,它占据了同样的内存量,只是它有一个 7 个 char
成员的数组。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
typedef struct {
char *p;
char c2;
} st1;
typedef struct {
char *p;
char c2;
char padding[7];
} st2;
printf("sizeof st1 = %zu\n", sizeof(st1));
printf("sizeof st2 = %zu\n", sizeof(st2));
exit(EXIT_SUCCESS);
}
输出:
sizeof st1 = 16
sizeof st2 = 16
在 C 语言中使用成员重新排序技术来节省对象的空间
前面的例子表明,当结构包括不同类型,并且没有填满对齐边界时,会有一些内存的浪费。虽然,在某些情况下,可能会重新排列结构成员,节省额外的空间。
接下来的示例代码中定义了 foo1
结构,它的中间是最大的成员(char *
),而 foo2
结构的成员与第一个结构相同。这两个对象的大小是不同的–24 字节和 16 字节。这与数据成员的排序有关。在 foo1
结构中,p
需要在被 8 整除的地址上对齐,所以前面的 int
和 short
总共会占用 8 个字节,后面的两个 char
也会占用这 8 个字节。虽然,如果我们把 p
移到第一位,下面的成员将挤进 8 个字节,也满足对齐规则。这样,foo2
的大小一共是 16 个字节,就叫打包 struct
。请注意,gcc
编译器有特殊的 __attribute__((packed))
指定符,该说明符甚至可以强制打包无序的 struct
成员。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
typedef struct {
int n1;
short s1;
char *p;
char c1;
char c2;
} foo1;
typedef struct {
char *p;
int n1;
short s1;
char c1;
char c2;
} foo2;
typedef struct {
int n1;
short s1;
char *p;
char c1;
char c2;
} __attribute__((packed)) foo3;
printf("sizeof foo1 = %zu\n", sizeof(foo1));
printf("sizeof foo2 = %zu\n", sizeof(foo2));
printf("sizeof foo3 = %zu\n", sizeof(foo3));
exit(EXIT_SUCCESS);
}
输出:
sizeof foo1 = 24
sizeof foo2 = 16
sizeof foo3 = 16