Alignment and Pack

长久以来,我都有这样一个观念,比如 pack(4),就代表着所有数据都是以 4 字节对齐的,直到我有了 64 位机,并且看到下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
struct One {
double d;
char c;
int i;
};

struct Two {
char c;
double d;
int i;
};

如果按 4 字节对齐的话,按我的观点,结构体中每一个成员都应该以 4 字节对齐,那么两个都是 4+8+4=16, 结果是对的。

那么如果按 8 字节对齐的话,结果是不是应该是 8+8+8=24?

可是错了。

正确的结果应该是 sizeof(struct One) = 16, sizeof(struct Two) = 24

为什么不一样呢?

pragma pack(n) 它指定了结构成员按 n(1,2,4,8,16)字节对齐,但它并不是指结构体中的每个成员都要按 n 对齐,而是按照每个成员的大小(align 值)和 n 相比较小的值对齐。

结构体还有另外一个需求,就是其大小必须是其成员 align 值最大者的整数倍。

那么上面的两个不同就很好解释了:

1
2
3
4
5
6
7
8
9
10
11
struct One {
double d; // 8 字节对齐占第 0-7 字节
char c; // 1 字节对齐占第 8 字节,另外 9-11 字节用于下面 int 对齐
int i; // 4 字节对齐占第 12-15 字节,16 是 8 的倍数,不需要 padding
};

struct Two {
char c; // 1 字节对齐占第 0 字节,另外 1-7 字节用于下面 double 对齐
double d; // 8 字节对齐占 8-15 字节
int i; // 4 字节对齐占 16-19 字节,20 不是 8 的倍数,需要加 4 个字节 padding
};

各种类型变量的 align 值可参考 Data Structure Alignment

再举一个结构体嵌套的例子:

1
2
3
4
5
6
7
8
9
10
11
12
struct Four
{
short s;
long l;
};

struct Five
{
char c;
struct Four four;
short s;
};

sizeof(struct Four)sizeof(struct Five) 在 4 字节对齐和 8 字节对齐时分别是多少呢?

我们以 8 字节对齐为例:

1
2
3
4
5
6
7
8
9
10
11
12
struct Four
{
short s; // 2 字节对齐占第 0-1 字节,另外 2-7 字节用于下面 long 对齐
long l; // 8 字节对齐占第 8-15 字节
}; // sizeof(struct Four) = 16

struct Five
{
char c; // 1 字节对齐占第 0 字节,另外 1-7 字节用于下面 four 对齐
struct Four four; // 8 字节对齐,占第 8-23 字节,结构体以其成员最大 align 值对齐
short s; // 2 字节对齐,占第 24-25 字节,26 不是 8 的倍数,需外加 6 字节
}; // sizeof(struct Five) = 32

如果在 32 位机中,struct Four 中,long 长度为 4, 于是 sizeof(struct Four) = 8, 并且最大 align 值是 4.

于是在 struct Five 中,four 是按 4 字节对齐的,虽然是 pack(8),但 4<8, 所以选 4 作为 align 值,sizeof(struct Five) = 16.

以上所有实验结果来自 x86_64 GNU/Linux,gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-48)。