知行LEARNING HANDBOOK
目录 · N03 C 结构体的内存布局、对齐与填充
学习手册/计算机基础/对象模型与内存布局
N036 分钟更新于 2026-08-20

C 结构体的内存布局、对齐与填充

通过具体布局理解 C 结构体的对齐、填充、字段偏移与数组排列。

cstructalignmentpaddingmemory-layout

建议先了解

  • C 基本类型
  • 指针基础

1. 这篇笔记解决什么问题

本篇回答 C struct 在真实内存里是如何组织的,以及为什么理解结构体布局是继续阅读 CPython 对象结构的前提。

学完后应能解释:成员是否连续;为什么出现 padding;为什么字段顺序改变 sizeof;指针成员和内嵌数组有什么区别;p.x 为什么可以被编译成“基地址 + offset”的访问。

2. 前置知识

  • byte、地址、指针;
  • C 基本数据类型。

待补充:ABI 对类型大小、对齐和调用约定的具体约束。

3. 核心概念

3.1 结构体是一段有固定布局的内存

struct A {
    int x;
    int y;
    int z;
};

在常见 int=4B 的平台上,概念布局:

offset 0      4      8      12
       ┌──────┬──────┬──────┐
       │  x   │  y   │  z   │
       └──────┴──────┴──────┘

成员按照声明顺序排列,但中间可能插入 padding。

3.2 对齐(Alignment)

某些类型通常希望从满足特定边界的地址开始。

常见示意:

char      size 1  alignment 1
short     size 2  alignment 2
int       size 4  alignment 4
double    size 8  alignment 8
pointer   size 8  alignment 8

[!WARNING] 这些值依赖 CPU/ABI/编译器目标,不能当成 C 语言跨平台固定保证。

3.3 填充(Padding)

struct A {
    char c;
    int x;
};

常见布局:

0       1 2 3       4              8
┌───────┬─────────────┬──────────────┐
│   c   │   padding   │      x       │
└───────┴─────────────┴──────────────┘
  1 B        3 B             4 B

因此结构体可能是 8B,而不是 5B。

3.4 尾部填充

struct B {
    int x;
    char c;
};

成员有效数据可能只有 5B,但结构体总大小仍可能补到 8B。原因之一是保证:

struct B arr[100];

数组中每个 arr[i] 的起始地址都满足结构体整体对齐要求。

3.5 成员偏移(Offset)

编译器在编译时可以知道:

x 在结构体起始地址后的多少字节

所以:

p.x

概念上接近:

address(p) + offset(x)

再对相应内存进行 load/store。

4. 直观理解

结构体可以想成一张“固定格式记录”:字段名只存在于源码/调试/符号层面,真正执行时关键是结构体起始地址与成员 offset。

类比边界:编译器优化后实际机器码不一定逐字面执行一次“加法再访问”,但地址计算语义等价。

5. 工作原理

5.1 字段顺序会影响总大小

struct A {
    char a;
    double b;
    char c;
};

在常见 8B 对齐的 double 平台上,可能出现:

a + 7B padding + b + c + 7B padding
≈ 24B

若重排:

struct B {
    double b;
    char a;
    char c;
};

可能为:

b + a + c + 6B padding
≈ 16B

这说明同样字段集合,不同排列可能导致不同 padding。

5.2 指针成员与内嵌数组

指针:

struct Person {
    int age;
    char *name;
};

结构体内主要保存 name 的地址:

struct Person
┌────────────┐
│ age        │
├────────────┤
│ name ptr ──┼────→ 另一处字符串数据
└────────────┘

数组:

struct Person {
    int age;
    char name[20];
};

20B 数组直接嵌在结构体中。

5.3 与 CPython 的关系

CPython 的大量对象底层就是 C 结构体。

对话中曾简化理解 Python 对象头为:

PyObject
├── 引用管理信息
└── 指向类型对象的指针

整数对象再在基础对象头后增加整数自身数据。

因此理解 struct + offset + pointer 是继续读 PyObjectPyLongObject 源码的基础。

6. 示例

示例:用 offsetof 验证

#include <stdio.h>
#include <stddef.h>

struct A {
    char c;
    int x;
    double y;
};

int main(void) {
    printf("sizeof(A) = %zu\n", sizeof(struct A));
    printf("c = %zu\n", offsetof(struct A, c));
    printf("x = %zu\n", offsetof(struct A, x));
    printf("y = %zu\n", offsetof(struct A, y));
}

输入:本机编译器和 ABI。

输出:结构体总大小和字段 offset。

说明:可直接观察 padding 对布局的影响。

7. 代码、命令或公式

若:

sizeof(struct A)=8sizeof(struct\ A)=8

则:

struct A arr[3];

三个元素起始地址概念上为:

base, base+8, base+16base,\ base+8,\ base+16

8. 容易混淆的概念

概念 A概念 B核心区别
字段大小字段 offsetoffset 还受前面字段和 padding 影响
padding有效数据padding 为布局/对齐服务,不是业务字段
char *namechar name[20]前者存地址,后者直接嵌入数据
struct 对象struct 指针前者是完整内存块,后者只是地址
C 成员访问Python 动态属性访问C offset 通常编译期确定,Python 属性查找更动态

9. 常见误区

误区:sizeof(struct) 就是所有字段大小直接相加

错误原因:忽略 alignment 和 padding。

正确理解:编译器可能插入成员间填充和尾部填充。

如何验证:使用 sizeofoffsetof

误区:char *name 把字符串内容放进结构体

错误原因:混淆指针与数组。

正确理解:结构体只保存指针值,字符串内容通常位于其他内存区域。

误区:所有 64 位平台结构体布局都一样

错误原因:忽略 ABI。

正确理解:必须以目标平台 ABI 或实际编译实验为准。

10. 与其他知识的关系

待建链接:ABI 与调用约定

11. 可以亲手完成的验证

目标:验证字段重排对结构体大小的影响。

struct A {
    char a;
    double b;
    char c;
};

struct B {
    double b;
    char a;
    char c;
};

打印两者 sizeof 和各字段 offsetof

预期:常见 ABI 下可能出现不同总大小和 padding。

实验不能证明:本机结果不能推广为所有 C 实现的固定规则。

12. 尚未解决的问题

  • C 标准与 ABI 分别保证哪些 struct layout 行为未系统区分。
  • #pragma pack、packed struct 和未对齐访问风险未讨论。
  • bit-field 布局未展开。
  • cache line、false sharing 与字段布局性能关系未展开。

13. 自测问题

  1. 为什么 char + int 的结构体可能不是 5B?
  2. 为什么结构体末尾也需要 padding?
  3. char *namechar name[20] 的内存含义有什么区别?
  4. 编译器为什么可以直接用固定 offset 访问成员?
  5. 为什么字段顺序会影响 sizeof
参考答案
  1. int 可能要求按特定边界对齐,因此 char 后会补 padding。
  2. 为保证结构体数组中下一个元素仍满足整体对齐。
  3. 指针字段只存地址;数组字段的数据直接属于结构体本身。
  4. 结构体类型和 ABI 在编译时确定成员相对起始地址的 offset。
  5. 不同排列会产生不同数量的成员间和尾部 padding。

14. 一句话总结

C struct 是一段受 ABI 对齐规则约束的固定内存布局;成员 offset、padding、指针与内嵌数据的区别,是理解 CPython 对象和系统内存结构的直接基础。