这篇文章介绍C语言结构体字节对齐的问题。

1 结构体字节对齐的规则

  1.  结构体变量的首地址能够被其最宽基本类型成员的大小所整除;???

  2. 数据类型自身的对齐:例如,在x86_64系统,char型数据自身对齐值为1字节,short型数据为2字节,int/float型为4字节,long/double/void*型为8字节。

  3. 结构体或者类的自身的对齐:其成员中自身对齐值中的最大值。

  4. 指定对齐值的情况下的对齐规则:#pragma pack (value)时的指定对齐值value。指定规则后以指定规则进行对齐。

    • 使用伪指令#pragma pack(n):C编译器将按照n个字节对齐;
    • 使用伪指令#pragma pack(): 取消自定义字节对齐方式。
  5. 数据成员、结构体和类的有效对齐值:若存在指定对齐值,需要自身对齐值和指定对齐值中较小者,即有效对齐值=min{自身对齐值,当前指定的pack值}。

2 x86环境各种基本数据类型的字节数

数据类型 x86 x86_64
char 1 1
short 2 2
int 4 4
long 4 8
float 4 4
double 8 8
void* 4 8

3 示例程序

当前的实例程序假定结构体首地址为0。环境关键信息如下:

  • Archithecture: x86_64

  • GCC version: 4.4.7

3.1 指定1字节对齐的示例分析

1
2
3
4
5
6
7
8
#pragma pack(1)
struct test {
    char a;  // not padding
    int b;   // not pading
    short c;
};
#pragma pack()
sizeof(struct test); // 7 字节

说明:

变量a,char自身对齐值为1, 指定值为1; 两者取小的值为1。首地址为0,变量a是对齐的,因此变量a占用1个字节。此时偏移1字节。

变量b, int类型自身对齐值为4, 指定值为1,两者取小的偏移值为1。当前偏移1字节,因此变量b前不需要填充,b占用4字节。此时偏移5字节。

变量c, short类型自身对齐值为2,指定值为1,两者取小的偏移单位为1。当前偏移5字节,因此变量c前也不需要填充,c占用2字节。此时偏移7字节。

结构体自身的对齐值取最大变量的长度(int),即4字节。但是指定的长度为1字节,两者取小即可得到整个结构体的对齐单位为1字节。因此,整个结构体占用7字节

3.2 指定2字节对齐的示例分析

1
2
3
4
5
6
7
8
#pragma pack(2)
struct test {
    char a; // pading 1Byte
    int b;  // not padding
    short c;
};
#pragma pack()
sizeof(struct test); // 8 字节

说明:

变量a,char自身对齐值为1, 指定值为2; 两者取小的值为1。首地址为0,变量a是对齐的,因此变量a占用1个字节。此时偏移1字节。

变量b, int类型自身对齐值为4, 指定值为2,两者取小的偏移值为2。当前偏移1字节,因此变量b前需要填充1字节,b占用4字节。此时偏移6字节。

变量c, short类型自身对齐值为2,指定值为2,两者取小的偏移单位为2。当前偏移6字节,因此变量c前不需要填充,c占用2字节。此时偏移8字节。

结构体自身的对齐值取最大变量的长度(int),即4字节。但是指定的长度为2字节,两者取小即可得到整个结构体的对齐单位为2字节。当前偏移长度为8字节,已经是对齐单位整数倍,所以无需填充。因此,整个结构体占用8字节

3.3 指定4字节对齐的示例分析

1
2
3
4
5
6
7
8
#pragma pack(4)
struct test {
    char a;
    int b;
    short c;
};
#pragma pack()
sizeof(struct test); // 12字节

说明:

变量a,char自身对齐值为1, 指定值为4; 两者取小的值为1。首地址为0,变量a默认是对齐的,因此变量a占用1个字节,无需填充。当前偏移1字节。

变量b, int类型自身对齐值为4, 指定值为4,两者取小的偏移值为4。当前偏移1字节,因此变量b前需要填充3字节,b占用4字节。当前偏移8字节。

变量c, short类型自身对齐值为2,指定值为4,两者取小的偏移单位为2。当前偏移8字节,因此变量c前不需要填充,c占用2字节。当前偏移10字节。

结构体自身的对齐值取最大变量的长度(int),即4字节。指定的长度为4字节,两者取小即可得到整个结构体的对齐单位为4字节。当前偏移长度为10字节,不是对齐单位整数倍,所以需要填充2字节。因此,整个结构体占用12字节

3.3 指定8字节对齐的示例分析

1
2
3
4
5
6
7
8
#pragma pack(8)
struct test {
    char a;
    int b;
    short c;
};
#pragma pack()
sizeof(struct test); // 12字节

说明: 变量a,char自身对齐值为1, 指定值为8; 两者取小的值为1。首地址为0,变量a默认是对齐的,因此变量a占用1个字节,无需填充。当前偏移1字节。

变量b, int类型自身对齐值为4, 指定值为8,两者取小的偏移值为4。当前偏移1字节,因此变量b前需要填充3字节,b占用4字节。当前偏移8字节。

变量c, short类型自身对齐值为2,指定值为8,两者取小的偏移单位为2。当前偏移8字节,因此变量c前不需要填充,c占用2字节。当前偏移10字节。

结构体自身的对齐值取最大变量的长度(int),即4字节。指定的长度为8字节,两者取小即可得到整个结构体的对齐单位为4字节。当前偏移长度为10字节,不是对齐单位整数倍,所以需要填充2字节。因此,整个结构体占用12字节

3.4 不指定字节,按照默认对齐的示例分析

1
2
3
4
5
6
7
struct test {
    char a;
    int b;
    short c;
    long d;
};
sizeof(struct test); // 24字节

说明: 变量a,char自身对齐值为1; 首地址为0,变量a默认是对齐的,因此变量a占用1个字节,无需填充。当前偏移1字节。

变量b, int类型自身对齐值为4。当前偏移1字节,因此变量b前需要填充3字节,b占用4字节。当前偏移8字节。

变量c, short类型自身对齐值为2。当前偏移8字节,因此变量c前不需要填充,c占用2字节。当前偏移10字节。

变量d, long类型自身对齐值为8。当前偏移10字节,因此变量d前需要填充6个字节,c占用8字节。当前偏移24字节。

结构体自身的对齐值取最大变量的长度(long),即8字节。无指定的对齐长度,因此整个结构体的对齐单位为8字节。当前偏移长度为24字节,是对齐单位整数倍,所以不需要填充。因此,整个结构体占用24字节