Skip to main content

Go: ABI

📅 2026-02-05 ✏️ 2026-03-06 Inside Go CS GO
No related notes

1 · Go ABIInternal: application binary interface#

internal-abi.md https://golang.org/design/40724-register-calling

Go’s ABI defines the layout of data in memory and the conventions for calling between Go functions. 定义数据在内存中的布局,函数之间的调用规约。

Go uses a common ABI design across all architectures. We first describe the common ABI, and then cover per-architecture specifics. 跨架构通用ABI设计,然后描述每个架构的特定性。

NOTE: ABIInternal 与 ABI0 可通过透明 ABI 包装器互相调用。ABI0 是面向汇编的稳定 ABI。

1.1 · 内存布局 Memory layout#

Size (sizeof) - 占用内存大小 表示一个类型在内存中实际占用的字节数。

Align (alignof) - 对齐要求 表示该类型的值在内存中必须从哪个地址边界(align的倍数)开始存放。

Offset (offset) - 字节偏移量 表示某个字段在结构体/序列中,相对于起始地址的字节位置。

三者关系总结:offset 是 size 和 align 共同作用的结果。

概念含义问题
size占用多少字节这个数据多大?
align起始地址必须是几的倍数这个数据从哪里开始放?
offset相对于结构体起始的字节位置这个数据具体在哪个相对位置?
unsafe.Sizeof()
unsafe.Offsetof()
unsafe.Alignof()

1.1.1 · 为什么需要对齐?

CPU 访问对齐的内存地址效率更高。例如:

  • 64 位 CPU 一次可以读取 8 字节
  • 如果数据从 8 的倍数地址开始,一次读取就能拿到
  • 如果不对齐,可能需要两次内存访问,还要拼接结果

NOTE: align 只需要满足数据的自然边界(4 字节数据对齐到 4 的倍数),而不是对齐到 CPU 的最大位宽。

定义:

alignof(S) = 1 if N = 0 = max(alignof(t_i) | 1 <= i <= N)

翻译:

  • 空序列(没有字段) align为 1,可以理解为不需要对齐,从任意位置放
  • 有字段 取所有字段中最大的 align

1.1.2 · 字节偏移量

定义:

offset(S, i) = 0 if i = 1 = align(offset(S, i-1) + sizeof(t_(i-1)), alignof(t_i))

翻译:

  • 第 1 个字段的 offset = 0(从起始位置开始)
  • 第 i 个字段的 offset = 上一个字段的位置 + 上一个字段的大小,然后对齐到当前字段要求的边界

1.1.3 · 字节占用量

定义:

sizeof(S) = 0 if N = 0 = align(offset(S, N) + sizeof(t_N), alignof(S))

翻译:

  • 空序列(没有字段) size为 1
  • 有字段 最后一个字段的结束位置,向上对齐到整个序列的 align

1.2 · 函数调用规约 Function call argument and result passing#

Function calls pass arguments and results using a combination of the stack and machine registers. 函数调用使用栈(内存)和机器寄存器的组合来传递参数和返回值。

访问寄存器通常比栈(内存)要快。参数和返回值优先通过寄存器传递。 但是,任何参数或者返回值超过可用寄存器的大小,都会被传递到栈上。

每一架构都定义了一系列的整数寄存器和浮点寄存器;

参数和返回值都递归分解为基础类型,再分配到寄存器中。

参数和返回值可以共享相同的寄存器,但不能共享相同的栈空间。 除了在栈上传递的参数和返回值外,函数调用者caller还为所有基于寄存器的参数保留在栈上的溢出空间(但不填充此空间 callee可能需要保存这些寄存器值)。

1.2.1 · 分配算法

核心流程:

  1. 初始化:设置整数寄存器索引 I=0,浮点寄存器索引 FP=0,栈序列 S 为空
  2. 分配参数:为接收器、参数逐个分配
  3. 分配结果:重置寄存器索引I/FP,为返回值分配
  4. 溢出空间:在栈S上为寄存器分配的参数预留空间(仅预留大小,不初始化值;caller分配, callee可用可不用)

NOTE: 分配优先级(尝试寄存器 → 失败降级到栈)

类型分配方案
布尔/单字整数I 寄存器
双字整数I, I+1 寄存器
浮点FP 寄存器
复数递归分配实部和虚部
指针/map/channel/函数I 寄存器
字符串/interface/slice递归分配内部组件
结构体递归分配各字段
长度1数组递归分配元素
长度0或>1数组无/失败

最终栈序列看起来像:栈分配的接收器,栈分配的参数,指针对齐,栈分配的返回值,指针对齐,所有分配到寄存器的值的保留溢出空间,指针对齐(低->高)。

+------------------------------+
|             . . .            |
| 2nd reg argument spill space |
| 1st reg argument spill space |
| <pointer-sized alignment>    |
|             . . .            |
| 2nd stack-assigned result    |
| 1st stack-assigned result    |
| <pointer-sized alignment>    |
|             . . .            |
| 2nd stack-assigned argument  |
| 1st stack-assigned argument  |
| stack-assigned receiver      |
+------------------------------+ ↓ lower addresses
  1. 调用者在栈低地址预留空间(超过寄存器数量或容量的参数)给被调用函数的栈帧
  2. 参数通过寄存器和栈传递
  3. 被调用函数返回前必须在指定寄存器/栈位置存储结果
  4. 没有被调用者保护寄存器(Callee-save)——调用其他函数会覆盖任何非固定寄存器
  5. 调用时某些区域(溢出空间、结果字段)未初始化,由被调用者负责初始化

1.2.2 · example#

https://github.com/golang/go/blob/master/src/cmd/compile/abi-internal.md#example

1.3 · 闭包

闭包的内存结构:函数值是指向闭包对象的指针

闭包对象包含:

  1. 指针大小的程序计数器(函数入口点)
  2. 捕获的环境变量(零个或多个字节

闭包调用机制:

  • 遵循静态函数/方法调用约定
  • 额外操作:调用前,将闭包对象地址存储到相应架构的闭包上下文指针寄存器中

NOTE: 本质上,闭包通过在调用时传递上下文指针(含环境信息)的方式来访问捕获的变量。

1.4 · 架构特定

https://github.com/golang/go/blob/master/src/cmd/compile/abi-internal.md#architecture-specifics