
1.2 编译和运行
如前一节所示,程序文本表示我们希望计算机做什么。同样,它只是我们写的另一段文本,并存储在硬盘的某处,但程序文本本身无法被你的计算机所理解。有一个特殊的程序,叫作编译器,它将C语言的文本翻译成机器可以理解的东西:二进制代码C或可执行文件C。这个翻译程序是什么样子的以及如何进行翻译都太复杂了,以至于在这个阶段很难解释[1]。即使在整本书中都无法解释其中的大部分内容,那将是另一本书的主题。然而,就目前而言,我们不需要更深入地理解,因为有工具为我们做所有的工作。
要点1.2 C是一种需要编译的编程语言。
编译器的名称及其命令行参数在很大程度上取决于运行程序的平台C。原因很简单:目标二进制代码依赖于平台C——它的形式和细节取决于要运行它的计算机。个人计算机和手机有不同的需求,冰箱和机顶盒所说的“语言”也不同。实际上,这就是C存在的原因之一:C为所有不同的机器特定语言(通常称为汇编语言C)提供了一个抽象级。
要点1.3 正确的C程序在不同的平台之间是可移植的。
在本书中,我们将花大量的精力向你展示如何编写“正确的”C程序来确保可移植性。不幸的是,有些平台自称是“C”,但不符合最新的标准。还有一些兼容的平台,它们接受不正确的程序,或者为C标准提供扩展,但这些扩展并不具有广泛的可移植性。因此,在单一平台上运行和测试程序并不能总是保证可移植性。
编译器的工作是确保前面的程序在适当的平台上翻译后,能够在你的PC、手机、机顶盒甚至冰箱上正常运行。
也就是说,如果你有一个POSIX系统(比如Linux或macOS),那么很有可能存在一个名为c99
的程序,而且它实际上是一个C编译器。你可以尝试使用以下命令编译示例程序:

编译器应该毫无怨言地完成它的工作,并在当前目录中输出一个名为getting-started
的可执行文件[练习1]。在示例行中:
c99
是编译程序。-Wall
告诉编译器,如果发现任何不寻常的事情,就向我们告警。-o getting-started
告诉编译器将编译结果C存储在一个名为getting-started
的文件中。getting-started.c
指明源文件C,该文件包含我们编写的C代码。注意,文件名末尾的.c
扩展名指的是C编程语言。-lm
告诉编译器在必要时添加一些标准的数学函数,稍后我们会用到。
现在我们可以执行C新创建的可执行文件C了。输入

你会看到和我之前展示的完全一样的输出。这就是可移植的含义:无论你在哪里运行这个程序,其行为C都应该是一样的。
如果你运气不好,编译命令不起作用,那么你必须在系统文档中查找编译器C的名称。如果没有可用的编译器,你可能需要安装它[2]。编译器的名称各不相同。以下是一些常见的替代方案,可能会有效:

其中有一些,即使存在于你的计算机上,也可能无法毫无怨言地编译程序[练习2]。
使用清单1.1中的程序,我们展示了一个理想的世界:一个在所有平台上都能工作并产生相同结果的程序。不幸的是,当你自己编程时,你经常会有一个只能部分工作的程序,可能会产生错误或不可靠的结果。因此,让我们看看清单1.2中的程序。它看起来和前一个很相似。
清单1.2 一个有缺陷的C程序的例子

如果你对这个程序运行编译器,它将会给出类似于如下内容的诊断C信息:

在这里,我们有很多很长的“告警”行,这些行太长了,甚至不能完全显示在终端屏幕上。最后,编译器生成了一个可执行文件。不幸的是,我们运行程序时的输出是不同的。这是一个信号,我们必须小心,注意细节。
clang
甚至比gcc
更讲究,它给出了更长的诊断行:

这是件好事!它的诊断结果C更丰富。特别是,它给了我们两个提示:它期望main
有一个不同的返回类型,它期望我们有一行代码(就像清单1.1中的第3行那样)来指定printf
函数来自哪里。注意clang
与gcc
不同,它不生成可执行文件。它认为第22行中的问题是致命的。将其视为一个特性。
根据平台的不同,可以强制编译器拒绝生成此类诊断的程序。对于gcc
,这样的命令行选项是-Werror
。
因此,我们已经看到了清单1.1和清单1.2的两个不同点,这两处改动将一个好的、符合标准的、可移植的程序变成了一个糟糕的程序。我们看到编译器也在帮助我们。它抓出了程序行中出现问题的行,如果有一点经验,你将能够理解它所告之的含义[练习3] [练习4]。
要点1.4 C程序应该在没有告警的情况下干净地编译。
总结
- C语言是用来给计算机下指令的。因此,它在我们(程序员)和计算机之间起到了中介作用。
- C程序必须经过编译才能执行。编译器提供了我们所理解的语言(C)与特定平台的特定需求之间的转换。
- C语言提供了一个提供可移植性的抽象级。一个C程序可以在许多不同的计算机架构上运行。
- C编译器是来帮助你的。如果它对你程序中的某些东西进行了告警,请加以注意。
[1]事实上,翻译本身是通过几个步骤完成的,从文本替换、适当的编译到连接。不过,将所有这些打包的工具传统上被称为编译器,而不是翻译器,因为这样会更准确。
[2]这对于使用微软操作系统的系统来说尤其必要。微软的本地编译器甚至还没有完全支持C99
,我们在本书中讨论的许多特性都无法工作。关于备选方案的讨论,你可以看看Chris Wellons的博客“Four Ways to Compile C for Windows”(Windows编译C的四种方法)。
[练习1]在终端中尝试编译命令。
[练习2]写一篇关于本书代码测试的文本报告。记下哪个命令对你有用。
[练习3]逐步纠正清单1.2中的问题。从第一个诊断行开始,修复在那里提到的代码,重新编译,等等,直到你有了一个完美的程序。
[练习4]我们还没有提到这两个程序之间的第三个区别。找到它。