C 语言 - 结构体对齐与填充
结构体填充
当您在 C 语言中创建一个结构体时,编译器可能会在成员之间添加一些额外的字节,即填充字节。
这样做是为了让程序在您的计算机上运行得更快,因为当数据在内存中正确对齐时,大多数 CPU 读取数据的效率更高。
除非您处理底层内存或文件格式,否则很少需要担心这个问题。
简而言之:填充意味着编译器有时会在结构体内部添加空白空间,以保持数据快速且在内存中正确对齐。
让我们看一个简单的结构体:
实例
struct Example {
char a; // 1 字节
int b; // 4 字节
char c; // 1 字节
};
int main() {
printf("Size of struct: %zu bytes\n", sizeof(struct Example));
return 0;
}
您可能期望的大小是 1 + 4 + 1 = 6 字节 - 但它通常会输出 12 字节!
为什么?
编译器添加填充字节,以便 int 成员 (b) 从是 4 的倍数的内存地址开始。这有助于 CPU 更快地读取它。
以下是内存的实际排列方式:
| 成员 | 字节数 | 说明 |
|---|---|---|
| a | 1 | 首先存储 |
| 填充 | 3 | 添加,使 b 从 4 的倍数地址开始 |
| b | 4 | 对齐到 4 字节边界 |
| c | 1 | 接下来存储 |
| 填充 | 3 | 添加,使总大小是 4 的倍数 |
总计 = 1 + 3 + 4 + 1 + 3 = 12 字节。
如何减少填充
填充取决于结构体中成员的顺序。如果您将较大的类型放在前面,可以使结构体更小:
实例
struct Example {
int b; // 4 bytes
char a; // 1 byte
char c; // 1 byte
};
int main() {
printf("Size of struct: %zu bytes\n", sizeof(struct Example)); // 通常为 8 字节
return 0;
}
现在结构体只有 8 字节,而不是 12 字节,因为需要的填充更少了。
为什么这很重要
- 填充通过保持数据在内存中对齐来帮助程序运行得更快。
- 它可能使结构体比预期的大。
- 重新排列成员可以减少总大小。
注意:除非您处理底层内存或文件格式,否则很少需要担心这个问题。
结构体与共用体对比
到目前为止,我们已经讨论了结构体内部的填充。但是共用体呢?
共用体将所有成员存储在同一个内存位置,因此成员之间没有填充。但是,结构体和共用体都仍然遵循对齐规则 - 数据必须从与其类型大小匹配的内存地址开始。
实例
struct S {
char a;
int b;
char c;
};
union U {
char a;
int b;
char c;
};
int main() {
printf("Struct size: %zu\n", sizeof(struct S));
printf("Union size: %zu\n", sizeof(union U));
return 0;
}
典型结果:
Struct size: 12 Union size: 4
解释:
- 结构体 - 成员一个接一个地存储,因此在它们之间添加了填充。
- 共用体 - 所有成员共享同一块内存,因此只有最大的成员决定其总大小。
| 特性 | 结构体 | 共用体 |
|---|---|---|
| 成员存储方式 | 一个接一个 | 所有成员共享同一内存空间 |
| 成员间填充 | 是 | 否 |
| 对齐依据 | 每个成员的类型 | 最大成员的类型 |
| 总大小 | 成员总和 + 填充 | 最大成员的大小 |
提示:您通常不需要担心这个问题,但这有助于理解为什么结构体有时会占用比预期更多的内存,而共用体则不会。
总结
- 结构体成员根据其数据类型大小进行对齐。
- 编译器可能会添加填充字节以正确对齐数据。
- 结构体的总大小通常大于其成员的总和。
- 重新排列成员可以减少填充并节省内存。