MemoryLayout 与对齐
检查类型的大小、步幅和对齐方式。
MemoryLayout 与对齐 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。
内存布局为何重要
使用不安全的 Swift或与 C 互操作时,您需要了解值在内存中的排列方式。Swift 提供了 MemoryLayout<T>,让您无需接触 raw 字节即可检查这些信息。
它可以回答三个问题:一个值有多大,数组中各值相隔多远,以及一个值必须从哪个地址边界开始。
// MemoryLayout works on any type
print(MemoryLayout<Int>.size) // 8 on 64-bit
print(MemoryLayout<Bool>.size) // 1
print(MemoryLayout<Double>.size) // 8大小 — 实际存储
MemoryLayout<T>.size 是值的有效数据所占用的字节数。它不包含数组元素之间可能存在的尾部填充。
当您想知道单个值包含多少有意义的字节时,请使用 size。
struct Point {
var x: Int8 // 1 byte
var y: Int64 // 8 bytes
}
print(MemoryLayout<Point>.size) // 16 (not 9 — padding!)stride — 数组中的间距
stride 是值在连续数组中紧密排列时,一个元素的起始位置与下一个元素的起始位置之间的字节距离。它等于向上取整到 alignment 后的 size。
手动分配缓冲区时,应乘以 stride,绝不能乘以 size。
print(MemoryLayout<Point>.stride) // 16
print(MemoryLayout<Int8>.stride) // 1
// Buffer for 10 Points needs:
let bytes = 10 * MemoryLayout<Point>.stride // 160alignment — 地址边界
alignment 是值必须开始的字节边界。alignment 为 8 的 Int64 必须从可被 8 整除的地址开始。编译器会插入填充来满足这一要求。
结构体的 alignment 是其成员中最大的 alignment。
print(MemoryLayout<Int8>.alignment) // 1
print(MemoryLayout<Int64>.alignment) // 8
print(MemoryLayout<Point>.alignment) // 8大小与 stride 的区别
对于许多类型,size == stride,但只要存在填充,两者就会不同。某个结构体的 size 可能是 9,而 stride 是 16,因为下一个元素必须按 8 字节对齐。
经验法则:使用 size 分析单个值,使用 stride 分析集合。
struct Mixed {
var flag: Bool // 1
var value: Int // 8
}
print(MemoryLayout<Mixed>.size) // 16
print(MemoryLayout<Mixed>.stride) // 16字段偏移量
对于结构体,您可以使用带有键路径的 MemoryLayout<T>.offset(of:),查询每个存储属性相对于起始位置的所在位置。当您需要让数据布局匹配 C 结构体时,这一点至关重要。
struct Header {
var magic: UInt32 // offset 0
var length: UInt64 // offset 8 (padded)
}
print(MemoryLayout<Header>.offset(of: \Header.magic) ?? -1) // 0
print(MemoryLayout<Header>.offset(of: \Header.length) ?? -1) // 8重新排列以节省空间
由于 alignment 的影响,存储属性的顺序会影响大小。将较大的字段放在前面,或按大小分组,可以缩小结构体的大小。对于大型数组而言,这一点很重要,因为浪费的字节会不断累积。
struct Wasteful { var a: Int8; var b: Int64; var c: Int8 } // size 24
struct Tight { var b: Int64; var a: Int8; var c: Int8 } // size 16
print(MemoryLayout<Wasteful>.size)
print(MemoryLayout<Tight>.size)引用的布局
类实例在堆上分配,因此保存类引用的变量实际上只是一个指针。无论对象有多大,它的 MemoryLayout.size 都是指针大小(8 字节)。
结构体等值类型会内联存储其内容;引用类型只存储指针。
class Big { var data = [Int](repeating: 0, count: 1000) }
print(MemoryLayout<Big>.size) // 8 — just the reference可选值与枚举
可选值通常会为标签增加一个字节,但 Swift 会优化引用类型的可选值,使其复用空指针,因此 String? 可能与 String 大小相同。
带关联值的枚举,其大小足以容纳最大的枚举情况及其判别值。
print(MemoryLayout<Int?>.size) // 9
print(MemoryLayout<String>.size) // 16
print(MemoryLayout<String?>.size) // 16 — no extra byte使用 of: 处理实例
每个成员(size、stride、alignment)也都有基于实例的形式:MemoryLayout.size(ofValue:)。当类型难以明确写出或由编译器推断时,这种形式非常方便。
let tuple = (UInt8(1), UInt64(2))
print(MemoryLayout.size(ofValue: tuple)) // 16
print(MemoryLayout.stride(ofValue: tuple)) // 16
print(MemoryLayout.alignment(ofValue: tuple)) // 8按正确布局分配
分配 raw 内存时,您必须同时传入字节数和 alignment。从 MemoryLayout 获取这些值,可以保证代码在不同架构上都正确,而不必硬编码数字。
let count = 4
let capacity = count * MemoryLayout<Int>.stride
let align = MemoryLayout<Int>.alignment
let raw = UnsafeMutableRawPointer.allocate(
byteCount: capacity, alignment: align)
defer { raw.deallocate() }快速检查
测试您对大小和 stride 的理解。
回顾
您已经学习了内存布局的三大支柱:
- 大小 — 单个值的有效字节
- stride — 数组元素之间的间距(分配时使用它)
- alignment — 必需的起始边界
您还了解了用于确定字段位置的 offset(of:)、属性顺序如何影响大小,以及引用实际上只有指针大小。这些工具可以帮助您安全地分配和解释 raw 内存。
常见问题解答
「MemoryLayout 与对齐」课时是免费的吗?
是的 — 「MemoryLayout 与对齐」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。
「MemoryLayout 与对齐」这节课中我会学到什么?
检查类型的大小、步幅和对齐方式。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Swift Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「MemoryLayout 与对齐」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Swift Academy 课中编写并运行代码吗?
能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。