Linus Torvalds 无疑是开源软件界最具影响力的人物之一。作为 Linux 内核的创始人,他因技术贡献赢得了尊敬,但也常因口无遮拦的言辞引发争议。
Linus 对代码质量的要求极其严苛,也许正是因为自信能够写出完美的代码,才让他有底气挖苦和讽刺其他开发者吧。
Linus 写出的代码到底能有多么精简、多么高深、多么优雅、多么健壮……?可能很多程序员都对此充满好奇。Linux 内核的代码显然过于复杂,不适合“鉴赏”。不过,好在 Linus 还在 2005 年 4 月(和 BitKeeper 闹翻之后)编写过 Git 的原型,而且 Git 最初版本的源代码(https://github.com/git/git/commit/e83c5163316f89bfbde7d9ab23ca2e25604af290)只有千行左右,算上 README 和 Makefile 才只有 11 个文件。这可真是揭晓答案的好机会。
$ wc -l *.c *.h | sort
23 cat-file.c
43 read-tree.c
51 init-db.c
66 write-tree.c
81 show-diff.c
93 cache.h
172 commit-tree.c
248 update-cache.c
259 read-cache.c
1036 total
不过,要是把这些源文件通读一遍还是需要花些功夫的。那就再从中挑一个最简单的来看一看。毕竟齐白石画只虾、画个萝卜也是名作。
最简单的应该是 init-db.c
了(别光看 cat-file.c
和 read-tree.c
的行数少,那里面调用的函数可复杂了)。经过编译后,init-db.c
形成了一个叫作 init-db
的命令。
试着运行一下就会发现,该命令的功能非常单一,类似现在的 git init
,仅仅是先在当前目录中创建一个隐藏目录 .dircache
,然后在里面创建 objects
目录,以及以两位十六进制数命名、名称依次为 00
~ff
的 256 个子目录。
$ tree -a .
.
`-- .dircache
`-- objects
|-- 00
|-- 01
|-- 02
...
|-- fc
|-- fd
|-- fe
`-- ff
对照着功能再来欣赏下 Linus 亲自操刀的 init-db.c
的代码,
... ...
int main(int argc, char **argv)
{
char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
int len, i, fd;
if (mkdir(".dircache", 0700) < 0) { // ① 创建 .dircache 目录
perror("unable to create .dircache");
exit(1);
}
... ...
// #define DEFAULT_DB_ENVIRONMENT ".dircache/objects" in `cache.h`
sha1_dir = DEFAULT_DB_ENVIRONMENT;
fprintf(stderr, "defaulting to private storage area\n");
len = strlen(sha1_dir);
if (mkdir(sha1_dir, 0700) < 0) { // ② 创建 .dircache/objects 目录
if (errno != EEXIST) {
perror(sha1_dir);
exit(1);
}
}
path = malloc(len + 40); // ⚠️注意
memcpy(path, sha1_dir, len);
for (i = 0; i < 256; i++) {
sprintf(path+len, "/%02x", i);
if (mkdir(path, 0700) < 0) { // ③ 创建 .dircache/objects/xx 目录
if (errno != EEXIST) {
perror(path);
exit(1);
}
}
}
return 0;
}
代码非常直观,①先调用 mkdir()
创建了 .dircache
,然后创建了 .dircache/objects
②,最后利用 for
循环,③创建了 256 个名字依次为 00
~ff
的、.dircache/objects
中的子目录。
但请注意这一行 path = malloc(len + 40);
。这里动态分配了一块内存,大小为 len + 40
,用于存储以十六进制数命名的子目录的路径。
稍有 C 语言编程经验的程序员都会牢记一条法则,如果使用 malloc()
分配了内存,但忘记调用 free()
来释放,就会造成内存泄漏。只要大学还在使用谭浩强的 C 语言教材开蒙,就是毫无项目经验的大一新生,也会因多次听到老师强调这一点,而囫囵吞枣地记住要 free()
吧。大名鼎鼎的 Linus 竟然忘了?
而且,搜索整个项目,可以发现不止一处调用了 malloc(), calloc(), realloc()
来申请内存,
$ egrep '(m|c|re)alloc' --color -rn .
./update-cache.c:65: active_cache = realloc(active_cache, active_alloc * sizeof(struct cache_entry *));
./update-cache.c:80: void *out = malloc(max_out_bytes);
./update-cache.c:81: void *metadata = malloc(namelen + 200);
./update-cache.c:138: ce = malloc(size);
...
却只 free()
过 1 次!
$ egrep 'free' -C1 --color -rn .
./show-diff.c-77- show_differences(ce, &st, new, size);
./show-diff.c:78: free(new);
./show-diff.c-79- }
Linus 是真忘了?这不妥妥地内存泄露吗!
等一等,malloc()
之后真的一定要 free()
吗?
其实,如果程序运行时间很短(就像 init-db
这样简单的工具),程序结束后操作系统就会回收所有已分配的内存(包括未释放的堆内存),所以不手动释放也不会对系统产生显著影响(但确实是不好的编程习惯)。
而 Linus 在初版 Git 中唯一一次 free()
是在 for (i = 0; i < entries; i++) {
这样一个循环中调用的。这说明即使程序不会长时间运行,但如果分配了大量内存或通过循环多次申请内存,还是应该及时 free()
,及时释放资源给其他进程。
总之,malloc()
之后并不是一定需要 free()
,这取决于具体的场景和需求。下回面试时万一忘记写 free()
,就可以辩解了,“Linus 当初创造 Git 时也没写,我们这是特意不写的”。但结果嘛……祝你好运。
不过,还有个好奇的点,path = malloc(len + 40)
这里为啥要 + 40
呢,追加的 /[0-f][0-f]
明明只占 3 个字节呀。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。