编程语言研究书库

第 3 章拆解 — 指针、内存布局与二进制格式

指针、内存布局与二进制格式

这一章讲什么: 把「内存」从一个看不见的资源,变成你能指着说清楚的东西: 一个变量在栈上还是在堆上、一个指针加 1 到底移动几个字节、 一个 ZIP 文件的末尾为什么藏着全书的目录、以及怎么让编译器在构建的那一刻就保证你的结构体和线上格式一个比特都不差。 这一章的原话是:你会看着一个二进制文件格式看见一个结构体,看着一次分配问出谁拥有它、什么时候释放、不释放会怎样1。

主线例子:zunzip,一个 ZIP 文件列表器。一个真实的 12 文件压缩包,从「跳到文件末尾找签名」开始, 一步步走到「打印出 DSW00286.png 21032530 bytes (c)」。

1. 顶层全景:zunzip 为什么必须从文件末尾读起

  普通文本文件:  字节 0 ──────────────→ 末尾(顺序读)
  ZIP 压缩包:    [本地文件头+数据] × N  ……  [中央目录]  [EOCD 记录]
                                              ↑            ↑
                                        (每个文件的索引)  (整个包的地图,固定 22 字节,在文件最末尾)

  zunzip 三步:  ① 跳到末尾读最后 ≤65557 字节 → ② 倒着扫找签名 0x06054b50 → ③ 按 EOCD 里的 cd_offset
                定位中央目录,循环读 46 字节固定头 + 跳过变长字段

图说:ZIP 是倒着造的——主索引(中央目录)在文件尾部,目录末尾的 EOCD(中央目录结束)记录只有 22 字节,是整个压缩包的「地图坐标」。这个设计可以追溯到软盘时代2。所以解析器不能从开头顺读,必须先跳到末尾找到 EOCD,再跳回中央目录,逐条读出文件名。

2. 核心原理

2.1 栈与堆:默认值与显式值

Zig 里声明一个结构体变量,默认它就住在栈上:没有申请内存的调用,也没有配对的释放调用,变量只活在函数的作用域里,函数一返回就自动清理——这是内存使用的「快车道」3。要住堆,语法就变了:allocator.create(Person) 申请一块够放一个结构体的内存、返回指向它的指针,紧跟着必须 defer allocator.destroy(ptr) 归还——你显式要了这块内存,就显式负责还4。

访问方式也随之改变:指针要用 heap_ptr.* 解引用才能写入;读字段时编译器允许简写 heap_ptr.name,它明白你指的是指针所指结构体里的字段5。这个「申请-归还-解引用」的三件套,就是后面一切所有权讨论的语法底子。

2.2 指针运算的规矩:单项指针不许动,要动先换类型

C 里任何指针都能当数字加减,Zig 把这扇门半关了:标准的单项指针(*T)禁止算术运算;要算,必须显式地把指针类型强制转换成多项指针([*]T)——「指向一串连续元素的起点的指针,长度未知」6。

为什么值得费这个劲?看两个模式的区别就清楚了:

  • 滑动指针:ptrArr += 1 每轮让指针变量本身前进。注意加的是 1 个元素,不是 1 个字节——对 i32 就是前进 4 字节,对 i16 是 2 字节。书上实测输出 Element 0: i32@16d676c00 … Element 1: i32@16d676c04,地址恰好每次加 47。
  • 偏移寻址:first 用 const 锚在数组开头不动,每轮算 first + i 产生一个临时地址——需要随机访问、又不想丢掉起点引用时用这个8。

而这个手写算数的「正规替代」,是把指针交给 for (myPtr, 0..) |*item, i|——边界由编译器和运行时把守,你保住指针「不拷贝就能改原处」的性能,又不承担越界的险9。

还有一层类型层面的保险:普通指针 *T 保证永不为空;想表达「可能缺失」,必须用 ?*T(可选指针)显式声明,然后编译器逼你在解引用前处理空的情况——一整类「忘了检查 NULL」的运行时崩溃被从类型层面删掉10。

2.3 动态字符串怎么安全递给 C

第 1 章调 C 函数时用的是字符串字面量——Zig 的字符串字面量在编译期就带零结尾,直接兼容 C 的 const char*。但动态拼出来的字符串不行:Zig 切片是「指针 + 长度」的结构,后面紧跟的那一个字节是不是 0 完全没保证,直接递给 C 函数,它会一路读到撞上一个随机的 0 为止——很可能段错误11。

书的转换流程四步,每一步都不能省:

第 1 步  allocator.alloc(u8, fullPath.len + 1)   // 多申请 1 字节,专供结尾的 0
第 2 步  std.mem.copyForwards(…)                  // 把切片内容拷进去
第 3 步  buffer[fullPath.len] = 0                 // 亲手写下零结尾
第 4 步  @as([*:0]const u8, @ptrCast(buffer.ptr)) // 告诉类型系统「这块内存已是零结尾」

图说:最后那步 @ptrCast 是你对编译器立的保证——类型本身验不了结尾真有没有 0,验错了责任在人12。这和第 1 章「切片自动剥长度」是同一个故事的另一面:方向不同,规则不同。

2.4 手搓一个记账分配器:接口就这仨函数

标准库分配器覆盖绝大多数场景,但「分配器到底是什么」只有亲手造一个才知道。答案出人意料地平淡:不是编译器魔法,就是一个包含三个函数指针(申请、调整、释放)的普通结构体;任何结构体实现了这三个操作,就是一个能递给 std.ArrayList 和一切标准容器的分配器13。

书造的 TrackingAllocator 是一个包装分配器:自己不碰操作系统,把真正的申请转发给「父分配器」(例子用的是 page_allocator),只在中间插入记账逻辑——alloc() 里 @sizeOf(T) * n 累加进计数器,free() 里减掉,create()/destroy() 管单个对象14。实测输出一行行对得上账:

Memory: 0 bytes
Allocated 500. Current: 500      ← 250 个 i16 × 2 字节
Allocated 1000. Current: 1500    ← 250 个 i32 × 4 字节
500 freed. Current: 1000         ← 精确减回
Allocated 8. Current 1008        ← 一个 i64
…
Memory: 1008 bytes               ← 退出时的净占用

图说:这几个数全都能手工验算——250×2=500,250×4=1000,i64 一个 8 字节。计数器全程没算错过15。

书里也明说了它故意缺什么:计数器是普通整数、没有原子保护,多线程下会数据竞争;没有实现 resize(),不能参与切片的原地扩容。这些不是疏忽,是「Zig 从不替你默默做这种取舍」的示范16。更深一层的收获是架构性的:因为每次分配都显式、每个分配器都可替换,你可以不改解析器一行代码,给它换一个 TrackingAllocator 测分配行为、换一个 FixedBufferAllocator 验内存预算、换一个「第三次申请必失败」的分配器验证每条失败路径——不需要 mock 框架17。

2.5 zunzip:倒着扫签名,顺着跳字段

现在走主线例子。zunzip tsouk.zip 要列出 12 个文件(书上的真实压缩包,最大的成员是 DSW00286.png,压缩后 21032530 字节)18。全程四步:

第 ① 步,智能寻址。 不从头读。EOCD 固定 22 字节,但它后面可以跟最长 65535 字节的注释,所以签名可能「漂浮」在文件最后 65557 字节里的任何位置。程序算 search_len = min(文件大小, 65535 + 22),用 readPositionalAll 一次性把这一小截尾巴读进缓冲区——性能与压缩包大小无关,10 GB 的包和 10 KB 的包一样快打开19。

第 ② 步,倒扫签名。 从 buffer.len - 22(最后一个能放下完整 EOCD 的位置)开始,逐字节倒退,每处用 std.mem.readInt(u32, …, .little) 读 4 字节和签名 0x06054b50 比对。倒扫保证第一个命中就是真正的 EOCD——就算 ZIP 注释里恰好含同样的字节序列也骗不过它20。

第 ③ 步,按图索骥。 命中后解析出两个关键字段:cd_offset(中央目录从文件的哪个字节开始)和 num_entries_total(一共多少个文件)21。

第 ④ 步,循环走目录。 从 cd_offset 起,每次读 46 字节的固定头(中央目录文件头),校验签名 0x02014b50,读出 filename_len 把文件名读进 256 字节栈缓冲,然后把偏移一次跳过 filename_len + extra_len + comment_len——变长字段一概不读,纯指针算术跳过去,落到下一条目的起点。循环 num_entries_total 次,打印压缩大小和文件名22。

运行结果就是那张清单:12 行,每行一个文件,最后还暴露了 macOS 偷偷塞进去的 __MACOSX/._ 元数据文件——「用户看不见,但物理上就在文件结构里」23。

2.6 comptime:把布局校验钉死在每次构建上

这是全章最「Zig」的一节,先讲清楚它防的是什么。线上格式(二进制协议、文件格式)给每个字段规定了精确的字节偏移。实际开发中,一次「局部看完全合理」的修改——把序号字段从 u32 加宽成 u64、把两个 u32 字段换到一起提升可读性、在 u8 后面插一个 u32——编译照过,尺寸没变或补偿性不变,但解析器从此读错字节。这个 bug 不在改动点爆,而在生产环境收到一条畸形记录时爆24。

Zig 的办法是把校验塞进 comptime 块——编译期断言在构建时、在二进制第一个字节存在之前运行,每次构建无条件运行,绕不过去:

comptime {
    if (@sizeOf(Record) != 12) @compileError("Record 必须恰好 12 字节");
    if (@offsetOf(Record, "crc32") != 8) @compileError("crc32 必须位于线上偏移 8");
}

图说:书上的示例协议 ZMP 是 12 字节记录头(magic 0 偏移、version 2、kind 3、length 4、crc32 8),用 extern struct 强制 C 兼容布局——普通 Zig 结构体编译器有权插填充、有权重排,填充字节插进 version 和 kind 之间会让之后每个字段挪一位,毁掉每条消息,而不产生任何编译警告25。extern struct 把这份自由收走:字段按声明顺序、不插填充。校验一旦失败,@compileError 直接掐断构建,报错信息指名是哪个字段错了位置——修复发生在键盘上,而不是凌晨两点的调试器里26。

注意 zunzip 和 ZMP 用了两条不同的路:ZMP 直接 bytesToValue 覆盖解析(有 extern 布局 + comptime 断言兜着);zunzip 故意不用覆盖法,而是逐字段 readInt 显式填充普通结构体——因为覆盖法「看起来零拷贝又优雅,实则脆弱」,尺寸常量 22 和 46 直接取自 PKZIP 规范而不是 @sizeOf(那会带上编译器填充)27。两条路都合法,选哪条取决于你信什么兜底。

2.7 字节序:多字节数的「书写方向」

字节序回答一个问题:一个 4 字节整数,最高位那个字节存在低地址还是高地址?大端把最高位字节放前面(像人读十六进制 12 34 56 78 的顺序),小端把它放最后(x86、ARM64 都是小端)28。书上的一行代码就把本机看个底朝天:取 0x12345678,用 asBytes 直接别名它的内存,小端机器上打印出 78563412——字节整个倒过来了29。

三组转换函数记住就够出门:

函数干什么
std.mem.readInt(u32, bytes, .little)按指定序把 4 字节拼成整数(解析文件/协议时用)
std.mem.nativeToLittle / nativeToBig把本机整数转成指定序(写文件/发协议时用)
std.mem.bytesToValue(T, slice)整块字节拷成结构体实例(顺带解决对齐问题)

图说:ZIP 诞生于 MS-DOS 时代,严格小端;.little 参数不是风格选择,是正确性要求——写错了,签名校验直接失败,解析器一无所获30。

2.8 两种把字节变回结构体的路,差别只在「对齐」

解析二进制时,手里是一段 []u8,想让它变回有字段的结构体。两条路:

  • 指针强转 @ptrCast(@alignCast(slice.ptr)):零拷贝,直接在那块内存上把地址重新解释成结构体指针——但 @alignCast 是运行期断言,地址不满足结构体的对齐要求就会 panic。书里故意在数组前塞一个 0xFF 字节让切片从奇地址开始,演示的就是这种危险31。
  • 拷贝解出 std.mem.bytesToValue(T, slice):像 memcpy 一样把字节拷进一个崭新的、安全的栈上实例,慢一点,但奇地址也能用——解析紧凑排布、没有填充的文件格式的正解32。

对齐为什么是要命的事:在 ARM、SPARC 这类架构上,多字节类型必须坐在自己大小的整数倍地址上,否则 CPU 直接 fault 崩溃。Zig 把对齐编进指针类型本身,拒绝隐式转换,逼你做一个显式的架构选择:要么保证缓冲对齐、走零拷贝;要么付一次拷贝的代价走安全路33。

2.9 分配器基准:数字比直觉可靠

「该用哪个分配器」的直觉出了名的不可靠。书的基准:重复申请 1000 万个 i32(约 38 MiB)、跑 10 轮、只计时申请本身(释放挪在计时外),结果是34:

分配器平均申请耗时相对
DebugAllocator2711 µs基准(最慢)
PageAllocator1888 µs1.4× 快
ArenaAllocator476 µs5.7× 快
FixedBufferAllocator343 µs7.9× 快

图说:快的那两个用的都是撞针分配(只前进指针、不找空闲块、初始化后不再发系统调用),FBA 连记账都没有,所以最快;Arena 为了「能超出初始块继续长」维护了一个块链表,贵 133 µs,是要动态增长的合理价钱35。

三个读数时最容易忽略的 nuance,书都点了:

  • PageAllocator 赢 DebugAllocator 是因为这块申请大(一次 38 MiB 连续块直接问内核要,比调试分配器的桶记账便宜);换成每次 10 字节、百万次的负载,结果会反转——分配器性能不是绝对的,是负载形状的函数36。
  • DebugAllocator 的 2711 µs 不是浪费,是安全特性的成本;开发期无条件值得付,问题从来不是「生产该不该用它」(不该),而是「开发期留着它罚多少」——这里十轮总共约多 800 µs,人感知不到37。
  • 基准没测碎片化:长跑进程里 DebugAllocator/PageAllocator 会堆出碎片,总量够但凑不出连续大块;arena 和 FBA 结构性免疫——因为它们从不复用,只前进。对长跑服务器,这条免疫可能比速度更要紧38。

3. 作者的判断与证据

  • 判断: 内存管理不是安全话题,是关键的优化杠杆;分配器选择要从「实现细节」升格为「架构决策」39。证据:上面的四行基准数字 + 每条数字背后的机制解释。
  • 判断: comptime 校验「不只是把检查提前,而是改变了检查的性质」——运行期检查可以被跳过、被绕过、在重构中被遗忘;编译期断言每次构建无条件运行40。证据:ZMP 示例的完整 round-trip 输出(magic=0xBEEF、length=14、crc32=0xB2BE7B14 全部对账)。
  • 判断(坦白的): 指针能制造恶性 bug——「如果你的程序编译不过,算你走运;如果不走运且乱用指针,你就真有麻烦了」41。这是全书少见的直白的怕。

4. 边界与局限

  • zunzip 只支持标准 ZIP:4 GiB 以内、65535 个条目以内;ZIP64(64 位扩展)是另一种 EOCD 格式,留给读者42。
  • 文件名缓冲写死 256 字节,更长的名字被静默截断——书明说这是已知局限,生产工具应该报错或堆分配22。
  • TrackingAllocator 无线程安全、无 resize,是教学删减16。
  • EOCD 扫描用「首个命中即真」的简化,没有校验候选的 comment_len 是否吻合文件剩余长度——对真实压缩包够用,恶意构造的包可能骗过它20。
  • 基准是微基准:单一尺寸、申请即释放的循环;真实负载的分配模式(大小混合、生命周期交错)结论可能不同38。

5. 可带走的

  1. 声明即栈、create 才堆;堆上对象 defer destroy 跟着申请写。
  2. *T 不能算数,[*]T 才能;ptr += 1 前进的是 sizeof(T) 字节,不是 1 字节。
  3. 随机访问留锚点(first + i),顺序消费用滑动指针,日常循环交给 for 让编译器守边界。
  4. 「可能为空」必须写成 ?*T,空检查被编译器强制执行。
  5. 动态字符串递 C:多申请 1 字节、拷内容、亲手写 0、再 @ptrCast 立保证。
  6. 分配器 = 三个函数指针;包装模式(转发 + 插逻辑)比裸写内存管理器好写也好组合。
  7. ZIP 从尾读:EOCD(22 字节、签名 0x06054b50)→ cd_offset → 46 字节头循环;变长字段用算术跳,不用读。
  8. 布局校验放 comptime:尺寸加总验字段宽度(@sizeOf 迭代求和),偏移逐个验(@offsetOf);线上格式用 extern struct。
  9. 解析字节用 readInt(…, .little);不知道对齐就用 bytesToValue 吃一次拷贝;信得过对齐才 @ptrCast。
  10. 分配器数字:大块偶发申请 PageAllocator 也行;热循环里 arena/FBA 快 6–8 倍;长跑服务把「碎片免疫」也算进选型。

6. 原文地图

主题原书章原文位置
本章主张:「看见结构体」Chapter 3: Direct Memory Access and Binary Formatstext/37-ch03-chapter-3-direct-memory-access-and-binary-format.txt(搜「You will be able to look at a binary file format and see a struct」)
单项指针禁止算术Pointer arithmetictext/38-fm-pointer-arithmetic.txt:15(搜「strictly forbid arithmetic」)
ptr += 1 前进 4 字节Pointer arithmetictext/38-fm-pointer-arithmetic.txt:132(搜「advances the pointer forward」)
可选指针 ?*TPointer arithmetictext/38-fm-pointer-arithmetic.txt:273(搜「guaranteed to never be」)
动态切片递 C 会段错误Composing Null-Terminated C stringstext/39-fm-composing-null-terminated-c-strings.txt:222(搜「segmentation fault」)
栈上结构体零申请Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:74(搜「fast path」)
create/destroy 配对Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:123(搜「responsible for giving it back」)
分配器=三函数指针Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:213(搜「vtable」)
TrackingAllocator 是包装器Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:806(搜「wrapper allocator」)
故意省略线程安全与 resizePutting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:828(搜「no thread safety」)
换分配器=免费插桩Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:866(搜「instrument any subsystem」)
ZIP 倒着造、EOCD 在末尾Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:920(搜「constructed backward」)
智能寻址 65557 字节窗口Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:1279(搜「65,557 bytes」)
倒扫签名首个命中即真Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:1349(搜「Scanning backward guarantees」)
46 字节头与变长跳跃Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:1513(搜「filename_len + header.extra_len + header.comment_len」)
comptime 三场景(加宽/重排/插字段)Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:1613(搜「reserves exactly 4 bytes」)
extern struct 不收填充Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:1737(搜「freedom to insert padding」)
修复发生在键盘上Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:1633(搜「2 am」)
字节序定义Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:2205(搜「sequential order」)
0x12345678 → 78563412Putting structures on stack and heaptext/40-fm-putting-structures-on-stack-and-heap.txt:2276(搜「reverse order」)
bytesToValue 解决对齐Reading Little-Endian integers from raw bytestext/41-fm-reading-little-endian-integers-from-raw-bytes.txt:527(搜「sidesteps this problem」)
对齐是硬件要求Reading Little-Endian integers from raw bytestext/41-fm-reading-little-endian-integers-from-raw-bytes.txt:600(搜「strict hardware requirement」)
分配器基准四行数字Reading Little-Endian integers from raw bytestext/41-fm-reading-little-endian-integers-from-raw-bytes.txt:1052(搜「2711」)
分配器性能是负载形状函数Reading Little-Endian integers from raw bytestext/41-fm-reading-little-endian-integers-from-raw-bytes.txt:1179(搜「load-shaped」)
碎片免疫Reading Little-Endian integers from raw bytestext/41-fm-reading-little-endian-integers-from-raw-bytes.txt:1237(搜「structurally immune」)

本章拆解依据原书第 3 章(含各小节)。每条出处可用 node scripts/book-verify.mjs systems-programming-with-zig 回核。

Footnotes

  1. 出处:「Chapter 3: Direct Memory Access and Binary Formats」(text/37-ch03-chapter-3-direct-memory-access-and-binary-format.txt,搜「see a struct」)。 ↩

  2. 出处:「Putting structures on stack and heap」第 920 段(text/40-fm-putting-structures-on-stack-and-heap.txt:920,搜「constructed backward」)。 ↩

  3. 出处:「Putting structures on stack and heap」第 74 段(text/40-fm-putting-structures-on-stack-and-heap.txt:74,搜「fast path」)。 ↩

  4. 出处:「Putting structures on stack and heap」第 123 段(text/40-fm-putting-structures-on-stack-and-heap.txt:123,搜「responsible for giving it back」)。 ↩

  5. 出处:「Putting structures on stack and heap」第 161 段(text/40-fm-putting-structures-on-stack-and-heap.txt:161,搜「pointing to」)。 ↩

  6. 出处:「Pointer arithmetic」第 15 段(text/38-fm-pointer-arithmetic.txt:15,搜「strictly forbid arithmetic」)。 ↩

  7. 出处:「Pointer arithmetic」第 132 段(text/38-fm-pointer-arithmetic.txt:132,搜「advances the pointer forward」)。 ↩

  8. 出处:「Pointer arithmetic」第 217 段(text/38-fm-pointer-arithmetic.txt:217,搜「without losing your reference」)。 ↩

  9. 出处:「Pointer arithmetic」第 243 段(text/38-fm-pointer-arithmetic.txt:243,搜「safely abstracts the pointer arithmetic」)。 ↩

  10. 出处:「Pointer arithmetic」第 273 段(text/38-fm-pointer-arithmetic.txt:273,搜「guaranteed to never be」)。 ↩

  11. 出处:「Composing Null-Terminated C strings」第 222 段(text/39-fm-composing-null-terminated-c-strings.txt:222,搜「segmentation fault」)。 ↩

  12. 出处:「Composing Null-Terminated C strings」第 266 段(text/39-fm-composing-null-terminated-c-strings.txt:266,搜「assertion by the programmer」)。 ↩

  13. 出处:「Putting structures on stack and heap」第 213 段(text/40-fm-putting-structures-on-stack-and-heap.txt:213,搜「vtable」)。 ↩

  14. 出处:「Putting structures on stack and heap」第 354 段(text/40-fm-putting-structures-on-stack-and-heap.txt:354,搜「middleman」)。 ↩

  15. 出处:「Putting structures on stack and heap」第 760 段(text/40-fm-putting-structures-on-stack-and-heap.txt:760,搜「verify the array logic first」)。 ↩

  16. 出处:「Putting structures on stack and heap」第 828 段(text/40-fm-putting-structures-on-stack-and-heap.txt:828,搜「no thread safety」)。 ↩ ↩2

  17. 出处:「Putting structures on stack and heap」第 866 段(text/40-fm-putting-structures-on-stack-and-heap.txt:866,搜「instrument any subsystem」)。 ↩

  18. 出处:「Putting structures on stack and heap」第 1537 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1537,搜「tsouk.zip」)。 ↩

  19. 出处:「Putting structures on stack and heap」第 1279 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1279,搜「65,557 bytes」)。 ↩

  20. 出处:「Putting structures on stack and heap」第 1349 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1349,搜「Scanning backward guarantees」)。 ↩ ↩2

  21. 出处:「Putting structures on stack and heap」第 1371 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1371,搜「cd_offset」)。 ↩

  22. 出处:「Putting structures on stack and heap」第 1499 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1499,搜「256-byte stack buffer」)。 ↩ ↩2

  23. 出处:「Putting structures on stack and heap」第 1571 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1571,搜「resource forks」)。 ↩

  24. 出处:「Putting structures on stack and heap」第 1587 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1587,搜「silently starts producing garbage」)。 ↩

  25. 出处:「Putting structures on stack and heap」第 1737 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1737,搜「freedom to insert padding」)。 ↩

  26. 出处:「Putting structures on stack and heap」第 1633 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1633,搜「2 am」)。 ↩

  27. 出处:「Putting structures on stack and heap」第 1125 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1125,搜「overlay approach is tempting」)。 ↩

  28. 出处:「Putting structures on stack and heap」第 2205 段(text/40-fm-putting-structures-on-stack-and-heap.txt:2205,搜「sequential order」)。 ↩

  29. 出处:「Putting structures on stack and heap」第 2276 段(text/40-fm-putting-structures-on-stack-and-heap.txt:2276,搜「reverse order」)。 ↩

  30. 出处:「Putting structures on stack and heap」第 2191 段(text/40-fm-putting-structures-on-stack-and-heap.txt:2191,搜「not a stylistic choice」)。 ↩

  31. 出处:「Reading Little-Endian integers from raw bytes」第 424 段(text/41-fm-reading-little-endian-integers-from-raw-bytes.txt:424,搜「runtime assertion」)。 ↩

  32. 出处:「Reading Little-Endian integers from raw bytes」第 527 段(text/41-fm-reading-little-endian-integers-from-raw-bytes.txt:527,搜「sidesteps this problem」)。 ↩

  33. 出处:「Reading Little-Endian integers from raw bytes」第 600 段(text/41-fm-reading-little-endian-integers-from-raw-bytes.txt:600,搜「strict hardware requirement」)。 ↩

  34. 出处:「Reading Little-Endian integers from raw bytes」第 1052 段(text/41-fm-reading-little-endian-integers-from-raw-bytes.txt:1052,搜「2711」)。 ↩

  35. 出处:「Reading Little-Endian integers from raw bytes」第 1191 段(text/41-fm-reading-little-endian-integers-from-raw-bytes.txt:1191,搜「bump-pointer allocation」)。 ↩

  36. 出处:「Reading Little-Endian integers from raw bytes」第 1179 段(text/41-fm-reading-little-endian-integers-from-raw-bytes.txt:1179,搜「load-shaped」)。 ↩

  37. 出处:「Reading Little-Endian integers from raw bytes」第 1141 段(text/41-fm-reading-little-endian-integers-from-raw-bytes.txt:1141,搜「roughly 800 µs」)。 ↩

  38. 出处:「Reading Little-Endian integers from raw bytes」第 1237 段(text/41-fm-reading-little-endian-integers-from-raw-bytes.txt:1237,搜「structurally immune」)。 ↩ ↩2

  39. 出处:「Reading Little-Endian integers from raw bytes」第 680 段(text/41-fm-reading-little-endian-integers-from-raw-bytes.txt:680,搜「critical optimization lever」)。 ↩

  40. 出处:「Putting structures on stack and heap」第 2167 段(text/40-fm-putting-structures-on-stack-and-heap.txt:2167,搜「changes their nature」)。 ↩

  41. 出处:「Reading Little-Endian integers from raw bytes」第 1259 段(text/41-fm-reading-little-endian-integers-from-raw-bytes.txt:1259,搜「nasty bugs」)。 ↩

  42. 出处:「Putting structures on stack and heap」第 1525 段(text/40-fm-putting-structures-on-stack-and-heap.txt:1525,搜「4 GiB」)。 ↩

On this page