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

解释:

  • 结构体 - 成员一个接一个地存储,因此在它们之间添加了填充。
  • 共用体 - 所有成员共享同一块内存,因此只有最大的成员决定其总大小。
特性 结构体 共用体
成员存储方式 一个接一个 所有成员共享同一内存空间
成员间填充
对齐依据 每个成员的类型 最大成员的类型
总大小 成员总和 + 填充 最大成员的大小

提示:您通常不需要担心这个问题,但这有助于理解为什么结构体有时会占用比预期更多的内存,而共用体则不会。

总结

  • 结构体成员根据其数据类型大小进行对齐。
  • 编译器可能会添加填充字节以正确对齐数据。
  • 结构体的总大小通常大于其成员的总和。
  • 重新排列成员可以减少填充并节省内存。