
1.3.2 编译
编译器(compiler)是一个程序,它接受源代码文件(对C语言来说是.c与.h文件),把文本形式的源代码转译成机器语言(machine language),并把机器语言跟预先定义的某些部件链接起来,纳入这些部件,让编译出的程序能够在特定的计算机硬件与操作系统上运行。编译器生成的结果是一个可执行文件,该文件是用机器语言编写的。
机器语言是一系列指令与数据,用来告诉中央处理器(Central Processing Unit,CPU)如何获取执行某程序所需的信息,以及如何按照正确的步骤执行相关的操作(从而实现程序想要的结果)。每种CPU都有自己的一套机器语言或指令集(instruction set)。用C语言开发程序时,程序员不需要知道机器语言的细节。
有人把汇编语言(assembler language,又称组合语言)称为机器语言,但这样说其实并不是特别准确,因为汇编语言(虽然也是用一系列指令与数据写成的)仍然含有文本和符号,而不像纯粹的机器语言那样仅包含二进制数字。从前,或许有很多程序员都能够看懂机器语言,但现在几乎没有这样的人了。
编译程序时,我们要调用编译器,让它处理一个或多个源文件。调用编译器会有两种结果:一种是顺利完成编译,并生成可执行文件;另一种是在编译过程中发现编程错误(programming error)。所谓编程错误可能是指某个名字拼错了,或某个标点漏掉了,但也有可能是指更为复杂的语法错误。一般来说,编译器在发现错误之后,会试着采用程序员能够理解的方式来描述这个错误,也就是说,它会提供有用的信息,帮助程序员意识到这个地方为什么写错了。当然,试错法只是一条大的原则,具体应该如何确定并修复错误还是有很多讲究的。有的时候,编译器可能会针对某个错误给出多行错误消息,而且你还要注意,每次调用编译器时,它都会把整个源代码全部处理一遍。因此,如果程序中有好多地方都有错,那么编译器会针对每个地方都给出相应的错误信息(你需要将这些错误信息与代码中有错的地方一一对应起来)。
一个完整且可以运行的程序是由编译之后的源代码与某些预先编译好的例程(routine)组成的,源代码是我们自己写的,而那些例程则由操作系统提供,它们是由操作系统的开发者制作的。这种预先定义好的程序代码也叫作运行时库或运行期库(runtime library),其中包含一套可以调用的例程,这些例程了解相关的细节,它们知道应该如何与计算机的各种组件交互。以Hello, world!程序为例,我们自己并不清楚在计算机屏幕上显示字符所用的详细指令——只需要调用一个预先定义好的函数(也就是printf()函数),因为这个函数知道这些细节。printf()函数是C语言运行时库的一部分,这个库中还有其他一些例程,我们后面会讲到。如何将文本发送到控制台需要根据操作系统来决定,不同的系统有不同的办法,就算硬件相同,只要安装的操作系统不同,就有可能需要用不同的办法去实现。因此,编译器还有一个好处在于,它不仅让程序员无须了解机器语言的细节,而且把计算机本身的某些实现细节掩盖了。
由此可见,每个操作系统都应该有特定的编译器与运行时库。专门为其中某个操作系统而设计的编译器很可能无法在另外一个操作系统上使用。如果你发现针对这个操作系统所设计的某款编译器碰巧能够(或者看上去似乎能够)运行在另一个操作系统上,那么这个编译器所制作出来的程序在那个系统中会有什么样的运行结果就很难说了。也许会造成严重的后果。
1.3.2.1 每个操作系统都有多种C编译器可供选择
你可以在多个计算机平台上学习C语言。UNIX与Linux操作系统上常见的C语言编译器是GNU编译器套装(GNU Compiler Collection,GCC),以及基于LLVM compiler project的clang。在Windows操作系统上,你可以通过Cygwin Project或MinGW Project所提供的技术来使用GCC。另外,你还可以在Raspberry Pi(树莓派)与Arduino等单片机系统上学习C语言,但由于这种计算机系统是极简式的,因此有许多特殊的地方需要考虑,它们不太适合用作学习C语言的平台。笔者还是推荐你采用桌面计算机来学习C语言,这种计算机通常可以运行网页浏览器,它们所配备的计算机资源(例如内存、硬盘空间、CPU等)较为充裕。
1.3.2.2 是否应该使用IDE来学习C语言
许多操作系统都会在安装某款IDE(Integrated Development Environment,集成开发环境)的过程中连带安装相关的编译器。IDE是安装在操作系统中的一套程序,这套程序能够帮助程序员创立、构建并测试他们想要开发的其他程序。IDE会管理与正在开发的这个程序有关的一个或多个文件,而且本身还提供了自己的文本编辑器。IDE可以调用编译器并展示编译结果,也能够执行编译所得的成品程序。程序员在使用IDE来开发程序的过程中一般无须离开这套环境,因为它通常能够帮助开发者顺畅地把一个可以单独运行的程序给制作出来。
有多种IDE可供选择,例如Microsoft的Visual Studio(只适用于Windows系统)与Visual Studio Code(适用于多种操作系统)、Apple的Xcode(适用于macOS系统以及Apple的其他硬件平台)、Eclipse Foundation的Eclipse,以及Oracle的Netbeans等。每款IDE都支持用各种编程语言来开发程序。本书中的程序基本上是用同一款简单的IDE开发出来的,这个IDE就是macOS上的CodeRunner。
大家在初学C语言时不应该使用IDE。笔者不建议你在这个阶段使用IDE,有这样几个原因。首先,学习并使用某种IDE本身是件相当枯燥的事情,这件事可以等你把程序开发流程中的各环节都熟悉了之后再来完成。其次,虽然各种IDE都会提供一些类似的功能,但有时它们实现这些功能所用的方式有很大区别,而且同一项功能在不同的IDE上可能会表现出不同的特性,这需要花很多时间来讨论。总之,你应该先把C语言学好,然后再根据自己的情况选择IDE。
1.3.2.3 在Linux、macOS或Windows系统上安装编译器
下面讲解如何在主流的桌面计算机系统(也就是Linux、macOS与Windows)上安装C语言的编译器。如果你用的是其他系统,那就必须自己寻找安装方式。不过没关系,那些系统本身也想让用户使用起来比较方便,因此安装编译器的办法不会太复杂。
□Linux:
1.如果你用的是基于RPM(Red Hat Package Manager)的Linux系统,例如RedHat、Fedora、CentOS等,那就在命令行界面执行下面这条命令:

2.如果你用的是Debian Linux[1],那就打开Terminal窗口并执行下面这条命令:

3.安装完之后,在命令行界面输入这条命令,以判断安装是否正确:

4.上一条命令应该会告诉你,你安装的GCC或clang编译器是什么版本。无论安装了哪种编译器,只要那条命令能显示出编译器的版本,就说明安装没有问题。现在你已经可以在Linux系统上编译C语言的程序代码了。
□macOS:
1.打开Terminal.app程序,并在命令行界面中输入这条命令:

2.如果系统中还没有安装相应的开发工具,那么刚才那条命令会让系统引导你安装这些工具。
3.安装完之后,关闭Terminal窗口,然后重新开启一个窗口,在里面执行这条命令:

4.确认安装无误之后,你就可以在macOS系统中编译C语言的程序代码了。
□Windows:
1.从相应的网站安装Cygwin(http://www.cygwin.com)或MinGW(http://mingw-w64.org/)。从中任选一个安装就可以了。如果安装的是Cygwin,那么一定要安装GCC(GNU Compiler Collection)附加包,这样才能把编译器以及用GCC调试程序时所需的其他一些工具安装上。
2.安装完之后,打开Command Prompt(命令提示符)界面,并执行下列命令:

3.确认无误之后,你就可以在Windows系统上编译C语言的程序代码了。
(广义的)编译环节可以细分为两个环节,一个是(狭义的)编译,另一个是链接。前者会检查源代码的语法,并把源代码转换成近乎完备的可执行代码,后者会把这种近乎完备的机器代码与运行时库合并,以形成彻底完备的可执行代码。一般来说,我们在调用编译器的时候还会调用链接器(linker)。如果编译环节顺利完成(也就是没有出现错误),那么会自动进入链接环节。后面我们会看到,这两个环节都有可能会报错,有的错误出现在编译期(也就是编译环节),有的错误出现在链接期(也就是链接环节;或者说,出现在把程序的各个部件链接起来的时候)。
笔者后面讲解如何编译第一个C语言程序时会告诉大家怎样调用编译器。
在整本书中,每当我们写好一个程序时,笔者就会引导大家故意破坏程序代码,也就是故意让程序无法通过编译。这样一来,我们就能了解这种破坏方式会让编译器给出什么样的错误信息,进而知道某条错误信息意味着程序中可能出现了什么样的错误。明白了这个道理,大家就不会担心自己把程序弄乱了,因为我们只需要将刚才故意写错的地方调整回去,就能让程序顺利地编译。
[1] 也包括基于Debian的其他Linux系统,例如Ubuntu等。——译者注