1.8.3 大型编程项目

为了构建操作系统,每个.c被C编译器编译成一个目标文件。目标文件使用后缀.o,含有目标机器的二进制代码。它们可以随后直接在CPU上运行。在C的世界里,没有类似于Java字节代码的东西。

C编译器的第一道称为C预处理器。在它读入每个.c文件时,每当遇到一个#include指令,它就取来该名称的头文件,并加以处理、扩展宏、处理条件编译(以及其他事务),然后将结果传递给编译器的下一道,仿佛它们原先就包含在该文件中一样。

由于操作系统非常大(五百万行代码是很寻常的),每当文件修改后就重新编译是不能忍受的。另一方面,改变了用在成千个文件中的一个关键头文件,确实需要重新编译这些文件。没有一定的协助,要想记录哪个目标文件与哪个头文件相关是完全不可行的。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

幸运的是,计算机非常善于处理事务分类。在UNIX系统中,有个名为make的程序(其大量的变体如gmake、pmake等),它读入Makefile,该Makefile说明哪个文件与哪个文件相关。make的作用是,在构建操作系统二进制码时,检查此刻需要哪个目标文件,而且对于每个文件,检查自从上次目标文件创建之后,是否有任何它依赖(代码和头文件)的文件已经被修改了。如果有,目标文件需要重新编译。在make确定了哪个.o文件需要重新编译之后,它调用C编译器重新编译这些文件,这样,就把编译的次数减少到最低限度。在大型项目中,创建Makefile是一件容易出错的工作,所以出现了一些工具使该工作能够自动完成。

一旦所有的.o文件都已经就绪,这些文件被传递给称为linker的程序,将其组合成一个单个可执行的二进制文件。此时,任何被调用的库函数都已经包含在内,函数之间的引用都已经解决,而机器地址也都按需要分配完毕。在linker完成之后,得到一个可执行程序,在UNIX中传统上称为a.out文件。这个过程中的各种部分如图1-30所示,图中的一个程序包含三个C文件,两个头文件。这里虽然讨论的是有关操作系统的开发,但是所有内容对开发任何大型程序而言都是适用的。

阅读 ‧ 电子书库
图 1-30 编译C和头文件,构建可执行文件的过程