现代C:概念剖析和编程实践
上QQ阅读APP看书,第一时间看更新

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编译器。你可以尝试使用以下命令编译示例程序:

019-01

编译器应该毫无怨言地完成它的工作,并在当前目录中输出一个名为getting-started的可执行文件[练习1]。在示例行中:

  • c99是编译程序。
  • -Wall告诉编译器,如果发现任何不寻常的事情,就向我们告警。
  • -o getting-started告诉编译器将编译结果C存储在一个名为getting-started的文件中。
  • getting-started.c指明源文件C,该文件包含我们编写的C代码。注意,文件名末尾的.c扩展名指的是C编程语言。
  • -lm告诉编译器在必要时添加一些标准的数学函数,稍后我们会用到。

现在我们可以执行C新创建的可执行文件C了。输入

019-02

你会看到和我之前展示的完全一样的输出。这就是可移植的含义:无论你在哪里运行这个程序,其行为C都应该是一样的。

如果你运气不好,编译命令不起作用,那么你必须在系统文档中查找编译器C的名称。如果没有可用的编译器,你可能需要安装它[2]。编译器的名称各不相同。以下是一些常见的替代方案,可能会有效:

020-01

其中有一些,即使存在于你的计算机上,也可能无法毫无怨言地编译程序[练习2]

使用清单1.1中的程序,我们展示了一个理想的世界:一个在所有平台上都能工作并产生相同结果的程序。不幸的是,当你自己编程时,你经常会有一个只能部分工作的程序,可能会产生错误或不可靠的结果。因此,让我们看看清单1.2中的程序。它看起来和前一个很相似。

清单1.2 一个有缺陷的C程序的例子

020-02

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

020-03

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

clang甚至比gcc更讲究,它给出了更长的诊断行:

021-01

这是件好事!它的诊断结果C更丰富。特别是,它给了我们两个提示:它期望main有一个不同的返回类型,它期望我们有一行代码(就像清单1.1中的第3行那样)来指定printf函数来自哪里。注意clanggcc不同,它不生成可执行文件。它认为第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]我们还没有提到这两个程序之间的第三个区别。找到它。