这一篇在干嘛?
这是《C Primer Plus》的起点篇:不写复杂代码,先回答三个问题——C 语言从哪来、为什么值得学、计算机是怎么跑程序的。读完你会明白”源代码 → 编译 → 链接 → 可执行文件”这条流水线,为第 02 章解剖第一个程序打好底子。
C 从何而来(Whence C?)
1972 年,贝尔实验室(Bell Labs)的 Dennis Ritchie 在和 Ken Thompson 一起开发 Unix 操作系统时,创造了 C 语言。C 并非凭空出现——它源自 Thompson 的 B 语言。
C 的出身决定了它的性格:它是为工作中的程序员创造的实用工具,首要目标是”有用”。对比一下同时代的其他语言:
- Pascal:主要目标是给”良好的编程原则”提供教学基础;
- BASIC:设计得像英语,方便不懂计算机的学生学习。
这些目标都很好,但不总与”日常干活好用”兼容。C 走的是实用路线,这让它成为几代程序员的选择。
为什么选 C(Why C?)

设计特性
C 融入了计算机科学理论与实践公认 desirable 的控制特性,天然适合自顶向下(top-down)规划、结构化编程(structured programming)和模块化设计,写出的程序更可靠、更易理解。
高效(Efficiency)
C 程序往往紧凑且运行快。它甚至具备一些通常只有汇编语言(assembly language,一种用助记符表示特定 CPU 内部指令的语言)才有的精细控制力——你可以为”最快速度”或”最省内存”微调程序。
可移植性(Portability)
在 A 系统上写的 C 程序,拿到 B 系统上很少或无需修改就能运行。当年把 IBM PC 的 BASIC 程序搬到 Apple BASIC、或把大型机 FORTRAN 搬到 Unix 的人都懂:移植有多麻烦。而 C 的编译器覆盖了从 8 位微处理器到超级计算机的约 40 种系统。
当然,可移植不是绝对的:专门访问某类硬件(如显示器)或操作系统特性(如 Windows API)的那部分代码,通常不可移植。
强大灵活、面向程序员
- 大部分 Unix 操作系统本身就是用 C 写的;FORTRAN、Perl、Python、BASIC 等语言的编译器/解释器也多为 C 所写;
- C 能访问硬件、操作内存中的单个位(bit),运算符(operator)丰富;
- C 不像 Pascal 那样严格限制你——自由既是优点也是危险:许多任务(如数据形式转换)在 C 里很简单,但你也可能犯在其他语言里根本不可能犯的错。
缺点
优缺点的根源常常是同一个特性:
- C 的**指针(pointer)**让你能犯很难追踪的错误——“自由奉献的代价是永恒的警惕”;
- C 简洁 + 运算符丰富,意味着有能力写出极难读懂的代码(还真有年度”混乱代码大赛” Obfuscated Code Contest)。
C 的去向(Whither C?)
到 20 世纪 80 年代初,C 已是 Unix 小型机世界的主导语言,随后扩展到个人计算机和大型机。软件公司喜欢用 C 写文字处理、电子表格、编译器等产品——因为 C 程序紧凑高效、易修改、易适配新机型。
90 年代起,很多大项目转向 C++(在 C 之上嫁接面向对象编程,object-oriented programming)。C++ 几乎是 C 的超集,所以学好 C 也顺便学了大半个 C++。
尽管有 C++、Java 等新秀,C 依然是软件行业的核心技能:嵌入式系统(汽车、相机、DVD 机里的微处理器)、科学计算、Linux 开发,处处有 C。
计算机在做什么(What Computers Do)
学 C 之前,先花两分钟理解计算机的基本构造:
- CPU(中央处理器):干大部分计算活的部件;
- RAM(随机存取内存):工作台,临时存放程序和文件;
- 永久存储(如硬盘):断电也不丢的仓库;
- 外设(键盘、鼠标、显示器):你与计算机交流的通道。
CPU 的”人生”极其简单:从内存取一条指令 → 执行 → 再取下一条 → 执行……(GHz 级 CPU 每秒能这样干约十亿次)。CPU 有几个自己的小抽屉——寄存器(register),其中一个存放”下一条指令的内存地址”。
两个关键事实:
- 计算机里的一切都是数字。字符有数字编码,指令也有数字编码;
- 程序最终必须以 CPU 能懂的**机器语言(machine language)**表达。
想给计算机下命令,就得用机器语言写一张精确到步骤的清单。连”把两个数相加”都要拆成:
- 把内存位置 2000 的数复制到寄存器 1;
- 把内存位置 2004 的数复制到寄存器 2;
- 寄存器 2 加到寄存器 1,结果留在寄存器 1;
- 把寄存器 1 的内容复制到内存位置 2008。
而且每条指令还得用数字代码表示!幸好,机器语言编程的黄金时代早就过去了。
高级语言与编译器(High-level Languages and Compilers)
C 这样的**高级编程语言(high-level language)**让编程轻松许多:
- 不用数字代码写指令;
- 指令更接近人的思维方式。加两个数,你只需要写:
total = mine + yours;但计算机看不懂这行字,于是**编译器(compiler)**登场:它把高级语言程序翻译成计算机需要的机器语言指令。你负责高层思考,编译器负责烦琐细节。
编译器还带来一个巨大的好处:每种 CPU 有自己的机器语言,但每种机器都可以配一个对应的编译器。同一个 C 程序,配上不同编译器,就能在不同机器上运行——这就是”一次编写、到处编译”的可移植性来源。
使用 C 的七个步骤(Using C: Seven Steps)

第 1 步:定义程序目标
想清楚程序要做什么:需要什么信息、要做什么计算、要汇报什么结果。这一步用一般术语思考,别想具体语言。
第 2 步:设计程序
决定怎么实现:用户界面什么样?程序如何组织?数据如何表示、用什么方法处理?好的数据表示方式能让后面的编程轻松一大截。这一步依然别碰具体代码。
第 3 步:编写代码
把设计翻译成 C 语言。用文本编辑器创建源代码文件(source code file)。书中的示例长这样(现在看不懂没关系,第 02 章就懂了):
#include <stdio.h>
int main(void)
{
int dogs;
printf("How many dogs do you have?\n");
scanf("%d", &dogs);
printf("So you have %d dog(s)!\n", dogs);
return 0;
}这一步别忘了写注释(comment)——把解释直接嵌进源代码里。
第 4 步:编译
编译器把源代码变成可执行代码(executable code),即计算机的母语——机器语言。它还会顺带把 C 库(library)里的标准例程(如 printf()、scanf())纳入最终程序(准确说干这活的是链接器,下一节细讲)。编译器同时检查你的程序是不是合法的 C——发现错误就报告,并且不生成可执行文件。学会看懂编译器的报错,本身就是一项要练的技能。
第 5 步:运行程序
在 Unix/Linux 控制台下输入可执行文件名即可运行;在 IDE(集成开发环境)里点菜单或按键运行;也可以直接双击文件图标。
第 6 步:测试和调试
程序能跑,不代表跑得对。程序里的错误叫bug,找错改错的过程叫调试(debugging)。犯错是学习的自然组成部分——随着你水平提高,你的错误也会变得更强大、更隐蔽。出错的可能包括:设计错误、好想法实现错了、没考虑奇怪的输入、C 用法不对、打字错误、括号放错位置……
第 7 步:维护和修改
程序被用起来之后,你会想改它:修小 bug、加新功能、适配新系统。文档清晰 + 设计良好的程序,维护起来才不痛苦。
记忆口诀:先规划,后编码
原书特别提醒:初学者最爱跳过第 1、2 步直接写代码。程序简单时脑子能装下全过程,可程序一长,凭感觉写出来的就是丑陋、低效、难懂的代码。任务越大越复杂,规划就越重要——动笔前先用纸笔写下目标和设计,省下的时间是真金白银。
编程机制(Programming Mechanics)
源代码文件
用 C 写的程序保存在文本文件里,文件名要以 .c 结尾。文件名分两部分:点号前是基本名(basename),点号后是扩展名(extension)。例如 budget.c 里,budget 是基本名,c 是扩展名。注意 Unix 区分大小写:budget.c、BUDGET.c 是三个不同文件,而 BUDGET.C 不是合法的 C 源文件名(大写 C 不行)。
目标文件、可执行文件与库
C 把源代码变成可运行程序分两步:编译(compiling)和链接(linking)。
编译器先把源代码翻译成目标代码文件(object code file,简称 object file)。目标文件虽然已是机器语言,但还不能运行,缺两样东西:
- 启动代码(startup code):程序与操作系统之间的接口。同样的硬件,DOS 和 Linux 处理程序的方式不同,启动代码就不同;
- 库例程代码:
printf()之类的标准函数代码存在**库文件(library)**里,目标文件里只有”我要用 printf()“这句话,没有 printf 的真身。

**链接器(linker)**把三样东西合并成一个可执行文件:你的目标代码 + 系统的启动代码 + 用到的库代码(只提取你用到的那些函数)。
一句话总结:目标文件和可执行文件都是机器语言,区别在于目标文件只含你自己代码的翻译,可执行文件还包含库例程和启动代码。
Unix 系统上的流程
Unix 不自带编辑器,用 emacs、vi 等通用编辑器写一个 inform.c:
#include <stdio.h>
int main(void)
{
printf("A .c is used to end a C program filename.\n");
return 0;
}编译命令:
cc inform.c几秒后提示符返回,编译完成。用 ls 一看,多了个 a.out——这就是可执行文件。运行它:
a.out输出:
A .c is used to end a C program filename.
细节:cc 会生成与源文件同名的目标文件 inform.o,但单文件程序链接完成后它会被删掉;想保留 a.out 就要改名,否则下次编译会被覆盖。
Linux 与 Windows
Linux 上用的是 GNU 的 gcc 编译器,流程与 Unix 基本一致(通常还把 cc 设为 gcc 的别名)。
Windows 不自带 C 编译器,一般装集成开发环境(IDE)——自带编辑器、菜单化编译运行、出错时能定位到出问题的行。新建项目时选择控制台(Console)类型的项目,才能跑本书这类命令行程序。
新手坑:IDE 窗口一闪而过
在 Windows IDE 里运行程序,输出窗口经常在程序结束的瞬间自动关闭,什么都看不见。解决办法:在
return语句前加一行getchar();,它会让程序等你按一次 Enter 键再退出。如果程序此前刚读过你的输入(比如让你输入体重),可能需要写两次getchar()。
源文件必须是文本文件
无论什么系统,源代码文件必须是纯文本文件,不能是字处理软件的专有格式(后者塞满了字体、排版信息)。用记事本等文本编辑器没问题;用字处理器的话,要”另存为”纯文本,且留意有的软件会自动加 .txt 扩展名——要改回 .c。
语言标准(Language Standards)
K&R C
C 诞生之初没有官方标准,Kernighan 和 Ritchie 1978 年的《The C Programming Language》第一版成了公认标准,史称 K&R C 或经典 C。但它只定义了语言、没定义库,于是 Unix 自带的库成了事实标准。
第一个 ANSI/ISO 标准(C89/C90)
1983 年 ANSI 成立 X3J11 委员会,1989 年通过新标准,同时定义了语言和标准 C 库;ISO 在 1990 年采纳了实质相同的标准。这套标准因此被称为 C89(ANSI 批准年)或 C90(ISO 批准年)。
委员会有一组很有趣的指导原则,被称为”保持 C 的精神”:
- 信任程序员(Trust the programmer);
- 不阻止程序员做需要做的事;
- 保持语言小而简单;
- 一种操作只提供一种方法;
- 追求速度,哪怕牺牲一点可移植性。
C99 标准
1994 年开始修订,成果是 C99。三大目标:
- 国际化:支持国际字符集;
- 修正缺陷:把实际中已被验证的做法编入标准(比如适配 64 位处理器);
- 提高计算能力:改进科学与工程关键数值计算的适用性。
同时保持保守:最小化与 C90 和 C++ 的不兼容,让 C 保持精炼——用委员会的话说,“让 C++ 去做那个庞大而有野心的语言吧”。当时的多数编译器并未完全实现 C99 的全部特性,有些特性还需要修改编译器设置才能开启。
最佳实践: sticking to the standard
写 C 程序时坚持标准写法(如
int main(void)+return 0;),不要用某些编译器私放的写法(如void main())。标准写法保证你的程序从一台机器搬到另一台、从一个编译器换到另一个时都不出问题——这正是 C 可移植性红利的兑现方式。
本章小结
- C 是为程序员设计的、强大而简洁的编译型语言,优点是高效、可移植、灵活,代价是需要程序员自律;
- 计算机只懂机器语言,编译器负责把 C 源代码翻译成机器语言,链接器把你的目标代码、启动代码和库代码组装成可执行文件;
- 编程七步走:定目标 → 设计 → 写代码 → 编译 → 运行 → 测试调试 → 维护修改;
- C 标准经历了 K&R C → C89/C90 → C99 的演进。
新手坑:只动手不动脑
又要强调一次:不要跳过”定义目标”和”设计程序”直接写代码。这个坑在第 1 步时摔一跤最便宜——现在养成纸笔规划的习惯,等程序长到几百行再摔,代价就是几小时的迷茫和返工。
自测题(附答案)
1. 编程语境下的”可移植性”(portability)指什么? 答:指在某个系统上编写的程序,只需少量修改甚至不做修改,就能在其他系统上运行。C 是可移植性最好的语言之一,但专门访问硬件或操作系统特性的代码除外。
2. 源代码文件、目标代码文件、可执行文件三者的区别是什么? 答:源代码文件是你用 C 语言写的文本(如
inform.c);目标代码文件是编译器把源代码翻译成的机器语言文件,但缺少启动代码和库代码,不能直接运行;可执行文件是链接器把目标代码、启动代码和所需的库代码合并后的完整程序,可以直接运行。3. 编程的七个主要步骤是什么? 答:① 定义程序目标;② 设计程序;③ 编写代码;④ 编译;⑤ 运行程序;⑥ 测试和调试;⑦ 维护和修改。
4. 编译器(compiler)是干什么的? 答:把高级语言(如 C)源代码翻译成特定计算机的机器语言(可执行代码),同时检查程序的语法合法性,并(大多数系统上)自动调用链接器把库代码纳入最终程序。
5. 链接器(linker)是干什么的? 答:把你的目标代码、系统的标准启动代码、以及库文件中你实际用到的函数代码,组合成一个完整的可执行文件。