credis 是一个教学导向的开源项目:从一个空文件开始,用纯 C 一步步造出一个能通过网络访问、能持久化的迷你键值数据库——功能上像一个极简版 Redis。
它同时是一份"边造边学 C"的教程:每一章对应一个真实里程碑,跟着 commit 历史走,你能看到一个 C 项目怎么从"打印一行字"长成一个网络服务。这是连载第一篇。
为什么 2026 年还要学 C
老实说,写业务不需要 C。但只要你想搞明白这些问题——
malloc到底把内存放在哪了?为什么会泄漏?- 一个 TCP 服务器在
accept之前发生了什么? - Redis / Nginx / SQLite 这些"基础设施级"软件为什么全是 C 写的?
——你就绕不开它。C 几乎没有抽象层:你看到的每一行代码,和机器实际做的事情之间是接近一比一的映射。学 C 的本质不是学一门语法,是学"计算机到底怎么工作"。 上层语言的 GC、协程、闭包,学完 C 再回头看全是通透的。
但学 C 有个老大难:语法书看得懂,真实项目长什么样不知道。 孤立地练数组、指针、字符串,练完还是拼不出工程直觉。所以这个连载反着来:用一个连贯的、有用的软件当主线,让每个概念在"我需要它解决眼前的问题"时自然登场。
C 知识体系:一张地图
动手前先把地形看清楚。C 的知识体系可以切成五层,从内到外:
1. 语言核心——和其它语言长得像的部分
类型系统(int/char/float/指针类型)、控制流(if/for/while/switch)、函数、以及 C 特有的预处理器(#include、#define、#ifdef——真正编译前的纯文本替换)。这一层花两天翻完任何一本语法书就够,不值得恋战。
2. 内存模型——C 的灵魂,其它语言替你藏起来的部分
- 栈与堆:局部变量活在栈上,函数返回就没;
malloc出来的活在堆上,你不free它就一直在 - 指针:本质就是"内存地址 + 类型解释"。指针难不在概念,在于所有权和生命周期全靠你自己的脑子管理
- 手动内存管理:谁分配、谁释放、释放后不许再碰——90% 的 C 灾难(段错误、泄漏、悬垂指针)都出在这一层
这一层是学 C 的核心回报:你会永远搞懂"内存"这个词。
3. 数据组织——从原始内存到数据结构
struct 把相关数据打包、数组和字符串(C 字符串就是"以 \0 结尾的字节数组",没有更多魔法)、再往上就是手写链表、哈希表。C 标准库不带容器——你需要什么数据结构,就得自己造什么,这恰恰是最好的数据结构课。
4. 系统编程——和操作系统对话
文件 IO(open/read/write)、网络(BSD socket:socket/bind/listen/accept)、进程与并发。这一层的知识严格说不属于 C 语言,属于 POSIX / 操作系统,但 C 是离系统调用最近的语言,学这层没有比 C 更合适的载体。
5. 工程工具链——真实项目的地基
编译四阶段(预处理→编译→汇编→链接)、Makefile、调试器(GDB/LLDB)、内存检查(Valgrind / AddressSanitizer)。工具链不性感,但"会不会用调试器"是区分"写过 C"和"学过 C 语法"的分水岭。
地图 × 路线图
credis 的章节就是沿着这张地图爬升的:
| 章节 | 里程碑 | 对应知识层 |
|---|---|---|
| 0 | 项目骨架:能编译、能运行 | 工具链(编译流程、Makefile) |
| 1 | 内存里的 GET/SET(固定大小) | 数据组织(struct、数组、字符串、指针) |
| 2 | 动态存储(不再限制大小) | 内存模型(malloc/free) |
| 3 | 哈希表:让查找变快 | 数据组织(哈希、链表、冲突处理) |
| 4 | 变成 TCP 服务器 | 系统编程(socket、系统调用) |
| 5 | 支持 HTTP:浏览器能访问 | 系统编程(协议解析、状态机) |
| 6 | 持久化:重启不丢数据 | 系统编程(文件 IO、序列化) |
| 7 | 进阶:并发 / 自制内存分配器 | 更深的底层 |
选型上只有一条原则:能自己写的都自己写。哈希表、网络层、协议解析、持久化格式全部手写,不用 libevent、不用 HTTP 解析库——用库就学不到东西了。允许用的只有 C 标准库、POSIX 系统调用和构建调试工具(Make/GDB/Valgrind),那些是地基,不是作弊。
第 0 章:骨架
在写任何功能之前,先做一件看起来"没用"但极其重要的事:让项目能编译、能运行。
盖楼要先打地基、测水平线。软件也一样——先建立一个"永远能跑通"的最小状态,之后每加一点功能,都在这个能跑的基础上增量前进。这样你永远不会陷入"几百行代码堆在一起,一编译几十个错误,不知道从哪查起"的绝望。
这一章只创建三样东西:
credis/
├── src/main.c 只打印版本号,不做任何事
├── Makefile 敲 make 就能编译
└── docs/ 每一章的教学文档
main.c:全部代码就这几行
| |
唯一值得停下来讲的是 #define:它是预处理指令——在真正编译之前,预处理器把源码里所有 CREDIS_VERSION 做纯文本替换成 "0.0.1"。用它定义常量,比到处硬写字符串好维护:改版本号只改一处。
Makefile:告别手敲 gcc
你之前可能一直 gcc file.c -o file 手动编译。项目一旦变大(多个 .c 文件、一堆编译选项),手打命令就不现实了。Makefile 把编译规则写下来,make 帮你执行:
| |
三个值得记住的点:
-Wall -Wextra打开所有警告,永远开着。 编译器警告经常在帮你抓真 bug——用%d打印double这种隐性错误,就是警告抓出来的。C 编译器默认沉默得可怕,别让它沉默。$(wildcard src/*.c)自动收集源文件,以后加新.c不用改 Makefile。.PHONY声明all/run/clean是"伪目标"——它们是命令的名字,不对应真实文件;不声明的话,目录里真出现一个叫clean的文件,make clean就再也不执行了。
试一试
| |
能跑通,第 0 章就毕业了。这个"永远能跑"的状态是接下来所有章节的安全网。
下一章预告
第 1 章开始有"内容":用 struct 定义键值对,先用一个固定大小的数组存起来,实现最基础的 SET(存)和 GET(取)——你会第一次看到 C 怎么把相关数据打包在一起,以及 C 字符串到底是个什么东西。
仓库在 github.com/KerroKapple/credis,每章一个 commit + 一篇 docs/ 教学文档,watch 起来跟着走。有问题欢迎开 issue 讨论。
