Golang结构体内存布局

✅ 一句话总结:

Go 的结构体不是简单相加大小,而是根据对齐规则填充 padding(填充字节),最终大小必须是最大对齐值的倍数。

为什么要关心对齐

  • 你正在编写的代码在性能(CPU、Memory)方面有一定的要求
  • 你正在处理向量方面的指令
  • 某些硬件平台(ARM)体系不支持未对齐的内存访问

为什么要做对齐

  • 平台(移植性)原因:不是所有的硬件平台都能够访问任意地址上的任意数据。例如:特定的硬件平台只允许在特定地址获取特定类型的数据,否则会导致异常情况
  • 性能原因:若访问未对齐的内存,将会导致 CPU 进行两次内存访问,并且要花费额外的时钟周期来处理对齐及运算。而本身就对齐的内存仅需要一次访问就可以完成读取动作

在上图中,假设从 Index 1 开始读取,将会出现很崩溃的问题。因为它的内存访问边界是不对齐的。因此 CPU 会做一些额外的处理工作。如下:

  1. CPU 首次读取未对齐地址的第一个内存块,读取 0-3 字节。并移除不需要的字节 0
  2. CPU 再次读取未对齐地址的第二个内存块,读取 4-7 字节。并移除不需要的字节 5、6、7 字节
  3. 合并 1-4 字节的数据
  4. 合并后放入寄存器

从上述流程可得出,不做 "内存对齐" 是一件有点 "麻烦" 的事。因为它会增加许多耗费时间的动作

而假设做了内存对齐,从 Index 0 开始读取 4 个字节,只需要读取一次,也不需要额外的运算。这显然高效很多,是标准的空间换时间做法

一、先看结构体定义

复制代码
type Test struct {
    a bool   // 1
    b int32  // 4
    c int8   // 1
    d int64  // 8
    e byte   // 1
}

单独大小确实是:

1 + 4 + 1 + 8 + 1 = 15 字节

但 Go 不允许你这样省空间,因为「对齐」。

二、对齐规则最容易误解,看我简化后的版本

对齐规则(简化版)

① 每个字段必须放在 它对齐值的整数倍偏移地址上

|-------|----|-------|
| 类型 | 大小 | 对齐值 |
| bool | 1 | 1 |
| int8 | 1 | 1 |
| byte | 1 | 1 |
| int32 | 4 | 4 |
| int64 | 8 | 8 |

② 结构体整体大小必须是 最大对齐值(这里是 8) 的整数倍

三、按顺序计算内存布局(会发现 padding 出现了)

⭐ 步骤 1:字段 a(bool)

|----|-----|------|-------------|-------|----|
| 字段 | 对齐值 | 当前偏移 | 可放? | 放到偏移 | 占用 |
| a | 1 | 0 | 0 % 1 = 0 ✔ | 0 | 1B |

⭐ 步骤 2:字段 b(int32)

b 对齐值是 4

当前 offset=1

1 不是 4 的倍数,必须填 padding,填到 4 位置。

所以需要补:4 - (1 % 4) = 3 字节

|--------|----------------|
| 填充 | 新偏移 |
| +3 | offset = 4 |

然后放 b,占 4 字节:

offset = 4 + 4 = 8

⭐ 步骤 3:字段 c(int8)

int8 对齐值 = 1 → 没问题

c 放在 offset 8,占 1 字节

offset = 9

⭐ 步骤 4:字段 d(int64)

对齐值 8

当前 offset=9

但 9 % 8 = 1 ≠ 0

需要补齐到下一个 8 对齐地址:16

16 - 9 = 7 字节 padding

offset = 16

放 d,占 8 字节:

offset = 16 + 8 = 24

⭐ 步骤 5:字段 e(byte)

byte 对齐值=1,无需对齐

放在 offset=24,占 1 字节

offset = 25

⭐ 步骤 6:结构体整体对齐

结构体的对齐值 = 最大字段对齐值 = 8

但当前结构体大小=25

必须让 25 补齐到 8 的倍数:

下一个 8 的倍数是 32

所以需要额外 padding:

32 - 25 = 7 字节

最终结构体大小 = 32 字节

🎉 四、你看到的最终 layout(图示)

复制代码
a xxx | bbbb | c xxx | xxx | dddd dddd | e xxx | xxx
0     4      8       16      24        25     32(end)

更清晰一点(每段代表 4 字节):

复制代码
[ a . . . ] [ b b b b ] [ c . . . ] [ . . . . ]
[ d d d d ] [ d d d d ] [ e . . . ] [ . . . . ]

. 表示 padding(填充字节)

五、实验输出结果

复制代码
Test size: 32, align: 8

六、为什么 Go 有这种对齐机制?

为了 CPU 高效访问内存。

CPU 访问 misaligned memory(未对齐地址)速度会变慢,甚至在某些平台会直接报错。

所以 Go 编译器自动对齐字段,让结构体在内存中更高效。

如果想让结构体更节省空间怎么办?

👉 调整字段顺序(从大到小)

复制代码
type Test2 struct {
    d int64
    b int32
    a bool
    c int8
    e byte
}
相关推荐
资深web全栈开发4 小时前
并查集(Union-Find)套路详解
leetcode·golang·并查集·unionfind
moxiaoran57536 小时前
Go语言的递归函数
开发语言·后端·golang
朝花不迟暮6 小时前
Go基础-闭包
android·开发语言·golang
西京刀客8 小时前
go语言-切片排序之sort.Slice 和 sort.SliceStable 的区别(数据库分页、内存分页场景注意点)
后端·golang·sort·数据库分页·内存分页
黄昏单车9 小时前
golang语言基础到进阶学习笔记
笔记·golang·go
moxiaoran575318 小时前
Go语言结构体
开发语言·后端·golang
Tony Bai1 天前
Cloudflare 2025 年度报告发布——Go 语言再次“屠榜”API 领域,AI 流量激增!
开发语言·人工智能·后端·golang
小徐Chao努力1 天前
Go语言核心知识点底层原理教程【变量、类型与常量】
开发语言·后端·golang
锥锋骚年1 天前
go语言异常处理方案
开发语言·后端·golang
moxiaoran57531 天前
Go语言的map
开发语言·后端·golang