C 是一门编译型的通用编程语言,由 Dennis Ritchie 于 1972 年在贝尔实验室创造,用来编写 Unix 操作系统。它比同时代的几乎所有东西都活得更久,原因很简单:C 只比机器高出薄薄的一层。它让你直接访问内存和 CPU 的真实能力,同时读起来仍然像是人写的代码。
半个世纪后,它依然是用来构建其他语言的语言。
一个完整的 C 程序
用六行代码就能看出整门语言的样子。点击“运行”。
#include <stdio.h> 引入标准输入输出的声明,这样 printf 才能被识别。main 是每个 C 程序开始执行的地方。printf 输出文本,\n 是换行符,return 0 告诉操作系统程序已成功结束。这就是一个完整的、可以编译的程序:不需要用类包起来,底下也没有任何框架。
C 语言有什么不同
大多数现代语言都会把机器对你隐藏起来。C 刻意不这样做。
- 它被编译成本机代码。 C 程序会变成某种 CPU 的机器指令并直接运行,中间没有解释器,也没有虚拟机。
- 内存由你自己管理。 没有垃圾回收器。你申请内存,用完再归还。这会多一些工作,但这也是 C 程序占用内存如此之少、运行时间如此可预测的原因。
- 它很小。 C 大约只有 32 个关键字。你可以把整门语言装在脑子里,而 C++、Java 或 Python 都做不到。
- 几乎没有运行时。 C 程序可以运行在只有几 KB 内存、完全没有操作系统的微控制器上。
代价是用坦诚换掉了安全。C 会允许你读取数组末尾之后的内容,或使用已经释放的内存,而不会阻止你。这些错误都有名字:段错误、内存泄漏、未定义行为。学习 C 很大程度上就是学会避开它们。
C 语言运行在哪里
C 不是一件历史古董。此刻它就在你阅读本文的设备上运行着,而且不止一处。
- 操作系统。 Linux 内核大约有 3000 万行 C 代码。Windows 和苹果内核的大部分也是用 C 写的。
- 嵌入式系统。 汽车、温控器、医疗设备、路由器和洗衣机里的微控制器,绝大多数都用 C 编程。当整颗芯片只有 32 KB 内存时,C 往往是唯一现实的选择。
- 数据库。 SQLite、PostgreSQL、MySQL 和 Redis 都是 C 程序。
- 语言运行时。 Python 的官方解释器 CPython 是用 C 写的。Ruby 的 MRI、PHP 的 Zend 引擎和 Lua 的核心也是。当 Python “太慢”时,解决办法通常是写一个 C 扩展。
- 工具和库。 Git、curl、FFmpeg、OpenSSL、zlib,还有 SQLite:计算世界的基础设施层就是 C。
这也是 C 的应用二进制接口(ABI)如此重要的原因:几乎所有语言都能调用 C 函数。Python、Rust、Go、Java 和 JavaScript 都有办法调用 C 库。C 是各种语言之间的通用语。
为什么要学 C 语言
有两个实在的理由,面向的是不同的人。
如果你想从事系统软件开发,比如内核、驱动、嵌入式固件、数据库,C 就是这个领域的工作语言,这些方向的大多数招聘信息都会直接写明要求 C(通常还会同时要求 C++ 或 Rust)。如果读不懂 C,就很难为 Linux、PostgreSQL 或微控制器项目贡献代码。
如果你想更好地掌握自己已经在用的语言,C 会告诉你底层是什么。学过 C 之后,你会明白数组下标为什么从零开始,字符串究竟是什么,为什么按值传递大对象开销很大,栈和堆是什么,以及空指针到底指向哪里。这些概念在每种语言中都存在;只有在 C 里,它们是摆在明面上的,而不是被隐藏起来。
这个程序只有三行 C 代码,却完整地展示了变量是什么:位于某个地址上的、有名字的盒子。在大多数语言中,你甚至没法问出这个问题。
C 和 C++
C++ 于 1979 年以“带类的 C”起步,后来发展成现存规模最大的语言之一:类、模板、异常、运算符重载、STL、智能指针,以及一个比 C 的整份规范还要庞大得多的标准库。
两者在实践中的关系:
- C++ 保留了 C 的语法,所以
if、for、while、运算符、指针和数组在两者中看起来都一样。 - 大多数写得规范的 C 代码都能作为 C++ 编译,只有少数例外(比如 C++ 要求对
malloc的返回值做类型转换)。 - C++ 代码几乎从来都不是合法的 C 代码。
如果你打算两门都学,先学 C 是一个真正的优势:C++ 直接建立在 C 的内存模型之上,而 C++ 中最让人困惑的部分,指针、引用、拷贝语义、析构函数,都是在解决 C 清清楚楚展示给你的那些问题。
不过,C 在任何官方意义上都不是 C++ 的子集。它们是两个各自独立演进的标准,现代 C 还有一些 C++ 没有的特性(C99 引入的指定初始化器、restrict 指针和变长数组)。
哪个 C:标准版本
C 是标准化的语言,阅读老代码时,版本很重要。
| 标准 | 年份 | 值得了解的内容 |
|---|---|---|
| C89 / C90 | 1989 | 古老的基线。变量只能在代码块开头声明。 |
| C99 | 1999 | // 注释、随处声明变量、通过 stdbool.h 使用 bool、long long |
| C11 | 2011 | 线程、原子操作、更安全的库函数 |
| C17 | 2018 | C11 的 bug 修复版,如今最常见的默认版本 |
| C23 | 2024 | true/false/bool 成为关键字、nullptr、二进制字面量 |
本文档以 C17 为准,并在相关之处指出 C99 及以上的特性。编译器默认使用较新的版本,但你也可以明确指定:
gcc -std=c17 -Wall -Wextra hello.c -o hello
C 语言没有什么
理解 C 的一部分,是知道它缺少什么,因为几乎每个“我该怎么……”的问题,在这里的答案都和现代语言不同。
- 没有字符串类型。 C 字符串是一个以零字节结尾的
char数组。拼接两个字符串需要调用函数,还得操心目标空间够不够大。 - 没有集合。 没有列表、映射或集合。你只有固定大小的数组和指针;哈希表要么自己写,要么链接一个库。
- 没有异常。 函数通过返回一个特殊值来报告失败,比如
-1、NULL或状态码,调用方需要自己检查。 - 没有泛型和重载。 一个函数名只对应一个函数。所以标准库为三种不同类型分别提供了
abs、labs和fabs。 - 没有命名空间或模块。 所有非
static函数共享同一个全局命名空间,所以 C 库会给所有东西加前缀(sqlite3_open、png_read_info)。 - 没有垃圾回收器。 每个
malloc都需要一个对应的free,忘了就是内存泄漏。
这些都不是疏忽。其中一些是 C 拒绝携带的运行时机制(垃圾回收器、异常展开、可增长的字符串),这在很大程度上正是它能装进只有 2 KB 内存的芯片的原因;另一些,比如命名空间和泛型,则是在 C 设计完成之后才出现的语言特性,为了保持语言小巧而没有加入。
缓冲区是 32 字节,因为是你决定这就够了。C 不会检查,这一行代码就体现了这门语言的全部性格。
C 语言难学吗?
C 很小,但不留情面。需要记的语法不多,一周就能把整门语言读完。花时间的是自律:记清哪块内存归谁所有,记住字符串只是以零结尾的字节,在使用指针之前检查它是不是空指针。
C 的难点大多不在概念上,而在于它没有护栏:编译器不会把你从越界写入或悬空指针中救出来,所以你会学会精确。(第一次学指针确实要花不少力气,这很正常,指针一章会慢慢讲。)这种精确是一种能带到你以后使用的每一种语言中的能力。
常见问题
C 语言是什么?
C 是一门编译型的通用编程语言,由 Dennis Ritchie 于 1972 年在贝尔实验室创造。它让你直接控制内存,并且与 CPU 实际执行的操作紧密对应,所以操作系统、设备驱动和数据库至今仍用它来编写。
C 语言现在用来做什么?
操作系统内核(Linux,以及 Windows 和 macOS 的部分组件),汽车、家电和微控制器中的嵌入式固件,SQLite 和 PostgreSQL 等数据库,CPython 和 Redis 等语言运行时和软件,以及供其他语言调用的高性能库。
2026 年还值得学 C 语言吗?
值得,原因有两个。它仍然是系统开发和嵌入式开发的语言,这是一个规模大、薪资高的领域。而且它能教会你内存、指针和栈到底是怎么工作的,这些知识会让你更好地掌握其他所有语言,因为它们大多数都是用 C 实现的。
C 语言和 C++ 有什么区别?
C++ 最初是“带类的 C”,保留了 C 的大部分语法,同时加入了面向对象编程、模板、标准库等大量内容。C 要小得多:没有类、没有异常、没有泛型。大多数合法的 C 代码都能作为 C++ 编译,反过来则不行。