结构体成员变量的字节对齐

1
2
3
4
5
❯ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  • 本文讨论的对齐属性
1
2
3
#pragma pack(n)
__attribute__((packed));
__attribute__((aligned(8)));
  • 完整验证代码见文末
  • 在Linux环境下char字节,int字节,short字节,long long字节(long字节(位),字节(位))

实验1

无全局的#pragma pack(n)

实验结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct test {     // size=15
char x2; // 1
int x1; // 4
short x3; // 2
long long x4; // 8
} __attribute__((packed));

struct test1 { // size=20 \ size=24
char x2; // 4 = 1 + 3
int x1; // 4
short x3; // 4 = 2 + 2 \ 8 = 2 + 6
long long x4; // 8
};

struct test2 { // size=24 \ size=24
char x2; // 4 = 1 + 3
int x1; // 4
short x3; // 4 = 2 + 2 \ 8 = 2 + 6
long long x4; // 8 + 4 \ 8
} __attribute__((aligned(8)));

注释中所标注的是结构体中每个成员变量所占内存空间的大小(如位与位结果不同,左侧为位下运行结果)

  • test__attribute__((packed))取消字节对齐,所有成员紧凑排列;
  • test1系统中,默认字节对齐,则char后填充字节,short后填充字节,总共占字节;位系统中,为了保证long long字节对齐,要在short后填充字节,总共占字节;
  • test2__attribute__((aligned(8)));要求test2的起始地址是的倍数。
    • 位系统中,按照test1,结构体总共占字节。但考虑连续实例化test2的情况,第一个起始地址是的倍数,占字节,下一个的起始地址就不再是的倍数,因此需要在末尾再填充字节,总共占用字节;
    • 位系统中,按照test1,结构体总共占字节。可以满足再实例化的结构体起始地址是的倍数,不需要在末尾额外填充,总共占用字节;

实验2

1
#pragma pack(1)

实验结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct test {     // size=15
char x2; // 1
int x1; // 4
short x3; // 2
long long x4; // 8
} __attribute__((packed));

struct test1 { // size=15
char x2; // 1
int x1; // 4
short x3; // 2
long long x4; // 8
};

struct test2 { // size=16
char x2; // 1
int x1; // 4
short x3; // 2
long long x4; // 8
} __attribute__((aligned(8)));

全局的#pragma pack(1)表示默认紧凑排列,所以testtest1均占用15字节。

test2__attribute__((aligned(8)));要求起始地址字节对齐,需要在末尾额外填充字节,总共占字节。

实验3

1
#pragma pack(2)

实验结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct test {     // size=15
char x2; // 1
int x1; // 4
short x3; // 2
long long x4; // 8
} __attribute__((packed));
struct test1 { // size=16
char x2; // 2 = 1 + 1
int x1; // 4
short x3; // 2
long long x4; // 8
};
struct test2 { // size=16
char x2; // 2
int x1; // 4
short x3; // 2
long long x4; // 8
} __attribute__((aligned(8)));

全局的#pragma pack(2)表示默认按字节对齐排列,

  • test__attribute__((packed))取消字节对齐,所有成员紧凑排列;
  • test1:按字节对齐,则char后需填充字节,总共占字节;
  • test2:按test1,总共占字节,可以满足再实例化的结构体起始地址是的倍数,不需要在末尾额外填充,总共占字节;

附录:完整实验代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stddef.h>
#include <stdio.h>

// 1. Unrestrained
// 2. #pragma pack(1)
// 3. #pragma pack(2)

struct test {
char x2;
int x1;
short x3;
long long x4;
} __attribute__((packed));

struct test1 {
char x2;
int x1;
short x3;
long long x4;
};

struct test2 {
char x2;
int x1;
short x3;
long long x4;
} __attribute__((aligned(8)));

int main() {
struct test t;
printf("%zu, addr_x2=%p\n", offsetof(struct test, x2), &t.x2);
printf("%zu, addr_x1=%p\n", offsetof(struct test, x1), &t.x1);
printf("%zu, addr_x3=%p\n", offsetof(struct test, x3), &t.x3);
printf("%zu, addr_x4=%p\n", offsetof(struct test, x4), &t.x4);
printf("%zu\n", sizeof(struct test));

printf("******\n");

struct test1 t1;
printf("%zu, addr_x2=%p\n", offsetof(struct test1, x2), &t1.x2);
printf("%zu, addr_x1=%p\n", offsetof(struct test1, x1), &t1.x1);
printf("%zu, addr_x3=%p\n", offsetof(struct test1, x3), &t1.x3);
printf("%zu, addr_x4=%p\n", offsetof(struct test1, x4), &t1.x4);
printf("%zu\n", sizeof(struct test1));

printf("******\n");

struct test2 t2;
printf("%zu, addr_x2=%p\n", offsetof(struct test2, x2), &t2.x2);
printf("%zu, addr_x1=%p\n", offsetof(struct test2, x1), &t2.x1);
printf("%zu, addr_x3=%p\n", offsetof(struct test2, x3), &t2.x3);
printf("%zu, addr_x4=%p\n", offsetof(struct test2, x4), &t2.x4);
printf("%zu\n", sizeof(struct test2));

return 0;
}