写在前面
malloclab 的难度很大,我实现了书上的基础的隐式空闲链表和显式空闲链表,用时约 16 小时,得分如下:
1 | Perf index = 47 (util) + 40 (thru) = 87/100 |
实现的逻辑本身是简单的,难度最大的地方在于调试,所以我并没有进一步实现分离的空闲链表,因为调试起来实在是太麻烦了。算是体验了一下系统级编程的复杂性。
有趣的是,由于 fail fast 的编程习惯,我喜欢在代码里到处放 assert 和 print 语句,这帮我节省了不少调试时间,我甚至一次 gdb 都没用就把所有的 bug 都修完了。(话说回来我也不知道这种东西怎么用 gdb 调试)
本文会谈谈做这个 lab 时学到的一些编程技巧,不会太多谈 lab 本身,因为照着书上实现出来就好了。
宏编程
宏编程最需要关注的地方就是到处都要加括号。让我们以下面这行代码为例:
1 |
像这种就是有问题的,因为没给 p 加括号!比如说,GET((char _)bp - WSIZE) 的原本意图是让 bp 减去 WSIZE,但放到 GET 里就导致 bp 先被转换成 unsigned int 指针,再减去 WSIZE,从而让 bp 减去了 WSIZE _ sizeof(unsigned int)!
validation_check
这个 lab 可以看作在创建一个神奇的类,我们总会对类有一些 validation 要求(比如说在这个 lab 中,我们要求内存里不能有连续的空闲块),这时我们就可以写一个 validation_check 函数,它会检查这个类是否满足我们的 validation 要求。
那这个 validation_check 有什么用呢?在调试的时候,我们可以把它插入到各种地方,然后看它在哪里 fail,这能帮助我们更快定位 bug。一个小技巧是用二分的思路来插入 validation_check。
其他小技巧
写这个 lab 的时候用到了很多 6.102 的知识,果然软工知识超有用~比如说,写一个简练清晰的 specification 很有帮助,fail fast 特别有用。
还注意到了一个有趣的地方,size_t
在我的系统上是这样显示的:
1 | typedef unsigned long size_t |
据 claude 说,size_t
的具体定义取决于系统架构,这可能是为了可移植性考虑?
除此之外,我觉得还有一个重要的作用,就是给类型提供“别名”。有时我们希望类型有一个更具描述性的名称,而不只是“int”。