如今,Python 已经成为现代编程教育中最重要的语言之一。
AI 现在甚至可以在几秒钟内生成代码。
现代编程语言提供了强大的库、自动内存管理以及各种高级抽象,让开发者能够以前所未有的速度构建应用程序。
于是,一个很自然的问题出现了:
为什么现在还有必要学习 C 语言?
C 语言诞生于五十多年前。
它不像 C++ 那样内置类和面向对象机制。
它不像 Java 或 C# 那样提供自动垃圾回收。
它也不会自动保护程序员避免很多内存相关错误。
而且,与 Python 相比,用 C 编写程序通常需要做更多工作。
但是,C 语言至今依然非常重要。
它仍然广泛用于:
嵌入式系统
设备驱动程序
编译器
数据库
网络软件
固件
系统库
更重要的是,C 语言能够让程序员看到许多现代编程语言刻意隐藏起来的东西:
软件究竟是如何与底层计算机交互的。
这正是 C 语言今天仍然重要的原因。
-
现代编程语言隐藏了底层机器
来看一个非常简单的 Python 语句:
x = 10
通常情况下,Python 程序员并不需要考虑这个值具体存放在内存的什么位置。
你不需要手动为它分配内存空间。
你通常也不会去关心它当前位于哪个 CPU 寄存器中。
使用完之后,你也不需要显式释放它占用的内存。
Python 自动处理了大量底层复杂工作。
这也是 Python 最大的优势之一。
它允许程序员把精力集中在解决问题上,而不是不断考虑内存地址、CPU 指令以及硬件细节。
但是,这同样存在一个代价。
计算机底层的很多机制因此变得不可见。
C 语言则不同。
C 会暴露更多应用程序下面正在发生的事情。
从某些方面来说,这让 C 更难学习。
但与此同时,这也让 C 成为理解计算机实际工作原理的最佳编程语言之一。
-
C 让内存变得可见
当我们谈到内存时,C 与很多现代编程语言之间的差别会变得非常明显。
例如下面这条 C 语句:
int x = 10;
变量 x 保存了整数值 10。
但是,x 并不仅仅是一个抽象的变量名称。
它实际上存在于计算机内存中的某个位置。
而这个位置拥有一个具体的内存地址。
C 语言允许我们直接访问这个地址。
这就引出了 C 语言最重要的概念之一:
指针。
指针可以保存另一个对象在内存中的地址。
一旦程序员开始使用指针、数组、结构体以及动态内存,计算机的内存系统就会逐渐变得具体起来。
你会开始思考这样的问题:
这些数据到底存在哪里?
它占用了多少字节?
它的内存地址是多少?
谁负责管理这块内存?
什么时候应该释放它?
如果程序访问了错误的地址,会发生什么?
这些问题让 C 比很多现代语言更难。
但与此同时,它们也帮助我们真正理解计算机是如何管理数据的。
-
栈和堆
C 语言也为理解进程内存中的两个重要区域提供了一条非常自然的路径:
栈和堆。
假设一个函数声明了一个局部变量。
这个变量通常会存放在程序的栈区域中。
如果程序通过下面这样的函数动态申请内存:
malloc()
那么,这块动态分配的内存通常来自堆。
栈和堆的工作方式并不相同。
栈内存通常会随着函数调用和函数返回而自动管理。
而堆内存则可能一直存在,直到程序显式释放它。
因此,在 C 语言中,程序员需要理解:
malloc()
以及:
free()
很多现代编程语言会自动完成这些内存管理工作。
这样确实让编程变得更简单,也能够避免很多错误。
但是 C 会把这些机制暴露出来。
当你真正理解 C 中发生了什么之后,很多概念也会变得更容易理解,例如:
栈帧
动态内存
内存泄漏
缓冲区溢出
对象生命周期
这些原本看起来很抽象的概念,会逐渐变得非常具体。
-
C 非常接近机器指令
再来看一条简单的 C 语句:
c = a + b;
对于程序员来说,这看起来只是一个加法操作。
但是 CPU 并不能直接执行 C 源代码。
编译器必须首先把 C 程序翻译成机器指令。
从概念上来说,处理器可能需要完成这样的过程:
第一,获取 a 的值。
第二,获取 b 的值。
第三,把这些值放到 CPU 可以使用的位置。
第四,执行加法。
第五,把结果保存到 c。
到了汇编语言这一层,这个操作最终可能会使用类似下面的指令:
ADD
这时,我们就已经从编程语言进入了计算机体系结构。
一条 ADD 指令可能会指定两个源寄存器。
处理器从寄存器中读取数据。
这些数据进入算术逻辑单元,也就是 ALU。
ALU 执行加法运算。
然后,结果通过处理器的数据通路传送回来,并最终被写入目标位置。
因此,看起来非常简单的一条 C 语句:
c = a + b;
实际上可以带我们穿过多个计算机层次:
C 源代码
↓
编译器
↓
机器指令
↓
寄存器
↓
ALU
↓
数字逻辑
这也是 C 语言与计算机体系结构如此契合的重要原因。
C 足够高级,让人类可以高效地编写复杂软件。
同时,它又足够接近底层机器,让我们仍然能够看到程序下面真正发生了什么。
- 为什么操作系统至今仍然大量使用 C
C 与硬件之间的这种紧密关系,在操作系统中显得尤其重要。
操作系统内核必须直接管理计算机中最关键的资源。
它需要处理:
内存
CPU 调度
文件系统
硬件设备
中断
系统调用
网络
安全边界
操作系统代码经常需要非常精确地控制内存布局、数据结构、硬件寄存器以及处理器行为。
这正是 C 语言非常强大的地方。
C 允许程序员比较直接地控制内存和硬件,同时仍然拥有函数、循环、结构体以及模块化源文件等结构化编程能力。
Linux 内核就是最著名的例子之一。
但更重要的并不是“Linux 使用 C”这件事本身。
真正重要的是:
操作系统工作在非常接近硬件的层次。
而 C 从设计之初,就是为这种系统级编程环境服务的。
-
C 隐藏在大量现代软件下面
今天,很多程序员可能从来没有编写过大型 C 程序。
但是,他们每天都在使用大量由 C 编写的软件。
C 广泛存在于很多技术的内部或者底层,例如:
编程语言运行时
数据库引擎
网络库
嵌入式固件
设备驱动程序
编译器
系统库
一个程序员可能整天都在编写 Python,而一行 C 代码都没有写。
但是在这个 Python 程序下面,软件栈中的很多重要组件仍然可能依赖 C,或者依赖其他能够与 C 兼容的底层原生代码。
这揭示了一个非常重要的事实。
高级编程语言并没有消灭底层编程。
它们只是在底层编程之上增加了更多层。
-
C 和 Python 谁更好,本身就是一个错误的问题
人们经常问:
C 比 Python 更好吗?
通常来说,这本身就是一个不太合适的问题。
因为这两种语言从一开始就拥有完全不同的设计目标。
Python 希望尽可能隐藏程序员不需要关心的复杂细节。
C 则给予程序员更多直接控制权。
当开发速度、可读性、库生态以及生产效率非常重要时,Python 非常优秀。
而当内存控制、性能、硬件访问或者底层系统行为非常重要时,C 就非常强大。
如果简单概括:
Python 像是在说:
很多机器细节交给我处理。
而 C 更像是在说:
机器细节都在这里,你可以自己控制。
这两种设计哲学没有哪一种在所有情况下都更好。
它们解决的是不同的问题。
理解这两种方式,也能够帮助我们理解为什么现代软件系统中经常同时存在多种编程语言。
因为不同语言通常工作在不同的软件层次。
-
那么 AI 呢?
人工智能正在给编程带来另外一次巨大变化。
今天,AI 已经可以根据自然语言描述直接生成代码。
开发者可以要求 AI 编写一个 Python、C++、JavaScript 或 C 函数。
几秒钟之后,就可能得到一段可以运行的代码。
那么,这是不是意味着学习 C 已经没有必要了?
并不是。
AI 的确可能降低我们记忆大量编程语法的必要性。
但是与此同时:
理解代码真正做了什么,反而变得更加重要。
AI 可以帮你生成一个指针。
但你仍然需要理解:
这个指针究竟指向哪里?
AI 可以生成 malloc()。
但你仍然应该知道:
到底申请了什么内存?
AI 可以生成多线程代码。
但你仍然需要理解竞争条件、同步以及内存安全。
AI 甚至可以生成汇编语言指令。
但是,如果你完全不了解寄存器、指令、内存以及 CPU 行为,那么一旦代码出现问题,调试就可能变得非常困难。
代码变得越容易生成,
理解代码究竟在做什么,就越有价值。
-
初学者还应该学习 C 吗?
并不是每一个初学者都必须从 C 开始学习编程。
对于很多人来说,Python 是一种更容易入门的第一门编程语言。
Python 可以让初学者首先掌握:
变量
循环
函数
条件判断
算法
数据结构
而不需要一开始就处理复杂的手动内存管理。
但是,当你已经掌握了一些基本编程知识之后,再学习 C 就会非常有价值。
因为 C 会暴露很多高级编程语言经常隐藏的概念:
内存地址
指针
数据表示
数组和结构体
栈
堆
编译
机器指令
软件与硬件之间的关系
你并不需要成为一个专业的 C 程序员,才能从学习 C 中获益。
即使只是对 C 有一个中等程度的理解,也会让很多领域变得更容易理解,包括:
计算机体系结构
网络
编译器
嵌入式系统
性能优化
-
从编程走向机器
这也许才是 C 至今仍然重要的最核心原因。
C 在整个计算机技术栈中占据了一个非常特别的位置。
最上面是应用程序。
下面是编程语言和各种库。
再往下是操作系统。
操作系统下面是机器指令。
然后是 CPU 寄存器以及 ALU 这样的执行单元。
再往下,则是由逻辑门和晶体管构成的数字电路。
C 距离这些底层结构足够近,因此程序员可以一路向下追踪程序究竟是如何运行的。
一行 C 代码可以变成一条机器指令。
一条机器指令可以从 CPU 寄存器中读取数据。
寄存器可以把数据送入 ALU。
ALU 可以执行算术运算或者逻辑运算。
而这些运算最终又是由数字电路实现的。
整个路径大致可以表示成:
C 程序
↓
编译器
↓
汇编语言 / 机器指令
↓
CPU 寄存器
↓
ALU
↓
逻辑门
↓
一旦理解了这些连接关系,
编程和计算机体系结构就不再是两个完全割裂的学科。
它们只是同一个计算机系统中的不同层次。
Conclusion
C 很古老。
但古老并不等于过时。
Python 等现代编程语言通过隐藏大量底层细节,让编程变得更加容易。
AI 又进一步让代码生成变得更加简单。
而 C 语言今天依然有价值,恰恰是因为它做了几乎相反的事情:
它把很多底层细节暴露出来。
C 可以帮助程序员理解:
内存
地址
指针
数据表示
栈和堆
编译
机器指令
CPU 行为
软件与硬件之间的关系
如果你希望快速构建程序,Python 是一个非常优秀的选择。
如果你希望进一步理解计算机究竟在做什么,C 就非常有价值。
而当我们继续从 C 向下走,就会进入计算机更深的世界:
汇编指令、寄存器、ALU、操作系统以及内核。
从这里开始,
编程真正与机器连接起来。
