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

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