本节书摘来自异步社区《C Primer Plus(第6版)中文版》一书中的第1章,第1.8节,作者 傅道坤,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.8 编程机制
生成程序的具体过程因计算机环境而异。C是可移植性语言,因此可以在许多环境中使用,包括UNIX、Linux、MS-DOS(一些人仍在使用)、Windows和Macintosh OS。有些产品会随着时间的推移发生演变或被取代,本书无法涵盖所有环境。
首先,来看看许多C环境(包括上面提到的5种环境)共有的一些方面。虽然不必详细了解计算机内部如何运行C程序,但是,了解一下编程机制不仅能丰富编程相关的背景知识,还有助于理解为何要经过一些特殊的步骤才能得到C程序。
用C语言编写程序时,编写的内容被储存在文本文件中,该文件被称为源代码文件(source code file)。大部分C系统,包括之前提到的,都要求文件名以.c结尾(如,wordcount.c和budget.c)。在文件名中,点号(.)前面的部分称为基本名(basename),点号后面的部分称为扩展名(extension)。因此,budget是基本名,c是扩展名。基本名与扩展名的组合(budget.c)就是文件名。文件名应该满足特定计算机操作系统的特殊要求。例如,MS-DOS是IBM PC及其兼容机的操作系统,比较老旧,它要求基本名不能超过8个字符。因此,刚才提到的文件名wordcount.c就是无效的DOS文件名。有些UNIX系统限制整个文件名(包括扩展名)不超过14个字符,而有些UNIX系统则允许使用更长的文件名,最多255个字符。Linux、Windows和Macintosh OS都允许使用长文件名。
接下来,我们来看一下具体的应用,假设有一个名为concrete.c的源文件,其中的C源代码如程序清单1.2所示。
程序清单1.2 c程序
#include <stdio.h>
int main(void)
{
printf("Concrete contains gravel and cement.\n");
return 0;
}
如果看不懂程序清单1.2中的代码,不用担心,我们将在第2章学习相关知识。
1.8.1 目标代码文件、可执行文件和库
C编程的基本策略是,用程序把源代码文件转换为可执行文件(其中包含可直接运行的机器语言代码)。典型的C实现通过编译和链接两个步骤来完成这一过程。编译器把源代码转换成中间代码,链接器把中间代码和其他代码合并,生成可执行文件。C使用这种分而治之的方法方便对程序进行模块化,可以独立编译单独的模块,稍后再用链接器合并已编译的模块。通过这种方式,如果只更改某个模块,不必因此重新编译其他模块。另外,链接器还将你编写的程序和预编译的库代码合并。
中间文件有多种形式。我们在这里描述的是最普遍的一种形式,即把源代码转换为机器语言代码,并把结果放在目标代码文件(或简称目标文件)中(这里假设源代码只有一个文件)。虽然目标文件中包含机器语言代码,但是并不能直接运行该文件。因为目标文件中储存的是编译器翻译的源代码,这还不是一个完整的程序。
目标代码文件缺失启动代码(startup code)。启动代码充当着程序和操作系统之间的接口。例如,可以在MS Windows或Linux系统下运行IBM PC兼容机。这两种情况所使用的硬件相同,所以目标代码相同,但是Windows和Linux所需的启动代码不同,因为这些系统处理程序的方式不同。
目标代码还缺少库函数。几乎所有的C程序都要使用C标准库中的函数。例如,concrete.c中就使用了printf()函数。目标代码文件并不包含该函数的代码,它只包含了使用printf()函数的指令。printf()函数真正的代码储存在另一个被称为库的文件中。库文件中有许多函数的目标代码。
链接器的作用是,把你编写的目标代码、系统的标准启动代码和库代码这3部分合并成一个文件,即可执行文件。对于库代码,链接器只会把程序中要用到的库函数代码提取出来(见图1.4)。
图1.4 编译器和链接器
简而言之,目标文件和可执行文件都由机器语言指令组成的。然而,目标文件中只包含编译器为你编写的代码翻译的机器语言代码,可执行文件中还包含你编写的程序中使用的库函数和启动代码的机器代码。
在有些系统中,必须分别运行编译程序和链接程序,而在另一些系统中,编译器会自动启动链接器,用户只需给出编译命令即可。
接下来,了解一些具体的系统。
1.8.2 UNIX系统
由于C语言因UNIX系统而生,也因此而流行,所以我们从UNIX系统开始(注意:我们提到的UNIX还包含其他系统,如FreeBSD,它是UNIX的一个分支,但是由于法律原因不使用该名称)。
1.在UNIX系统上编辑
UNIX C没有自己的编辑器,但是可以使用通用的UNIX编辑器,如emacs、jove、vi或X Window System文本编辑器。
作为程序员,要负责输入正确的程序和为储存该程序的文件起一个合适的文件名。如前所述,文件名应该以.c结尾。注意,UNIX区分大小写。因此,budget.c、BUDGET.c和Budget.c是3个不同但都有效的C源文件名。但是BUDGET.C是无效文件名,因为该名称的扩展名使用了大写C而不是小写c。
假设我们在vi编译器中编写了下面的程序,并将其储存在inform.c文件中:
以上文本就是源代码,inform.c是源文件。注意,源文件是整个编译过程的开始,不是结束。
2.在UNIX系统上编译
虽然在我们看来,程序完美无缺,但是对计算机而言,这是一堆乱码。计算机不明白#include和printf是什么(也许你现在也不明白,但是学到后面就会明白,而计算机却不会)。如前所述,我们需要编译器将我们编写的代码(源代码)翻译成计算机能看懂的代码(机器代码)。最后生成的可执行文件中包含计算机要完成任务所需的所有机器代码。
以前,UNIX C编译器要调用语言定义的cc命令。但是,它没有跟上标准发展的脚步,已经退出了历史舞台。但是,UNIX系统提供的C编译器通常来自一些其他源,然后以cc命令作为编译器的别名。因此,虽然在不同的系统中会调用不同的编译器,但用户仍可以继续使用相同的命令。
编译inform.c,要输入以下命令:
cc inform.c
几秒钟后,会返回UNIX的提示,告诉用户任务已完成。如果程序编写错误,你可能会看到警告或错误消息,但我们先假设编写的程序完全正确(如果编译器报告void的错误,说明你的系统未更新成ANSI C编译器,只需删除void即可)。如果使用ls命令列出文件,会发现有一个a.out文件(见图1.5)。该文件是包含已翻译(或已编译)程序的可执行文件。要运行该文件,只需输入:
a.out
输出内容如下:
A .c is used to end a C program filename.
图1.5 用UNIX准备C程序
如果要储存可执行文件(a.out),应该把它重命名。否则,该文件会被下一次编译程序时生成的新a.out文件替换。
如何处理目标代码?C编译器会创建一个与源代码基本名相同的目标代码文件,但是其扩展名是.o。在该例中,目标代码文件是inform.o。然而,却找不到这个文件,因为一旦链接器生成了完整的可执行程序,就会将其删除。如果原始程序有多个源代码文件,则保留目标代码文件。学到后面多文件程序时,你会明白到这样做的好处。
1.8.3 GNU编译器集合和LLVM项目
GNU项目始于1987年,是一个开发大量免费UNIX软件的集合(GNU的意思是“GNU’s Not UNIX”,即GNU不是UNIX)。GNU编译器集合(也被称为GCC,其中包含GCC C编译器)是该项目的产品之一。GCC在一个指导委员会的带领下,持续不断地开发,它的C编译器紧跟C标准的改动。GCC有各种版本以适应不同的硬件平台和操作系统,包括UNIX、Linux和Windows。用gcc命令便可调用GCC C编译器。许多使用gcc的系统都用cc作为gcc的别名。
LLVM项目成为cc的另一个替代品。该项目是与编译器相关的开源软件集合,始于伊利诺伊大学的2000份研究项目。它的Clang编译器处理C代码,可以通过clang调用。有多种版本供不同的平台使用,包括Linux。2012年,Clang成为FreeBSD的默认C编译器。Clang也对最新的C标准支持得很好。
GNU和LLVM都可以使用-v选项来显示版本信息,因此各系统都使用cc别名来代替gcc或clang命令。以下组合:
cc -v
显示你所使用的编译器及其版本。
gcc和clang命令都可以根据不同的版本选择运行时选项来调用不同C标准。
gcc -std=c99 inform.c[3]
gcc -std=c1x inform.c
gcc -std=c11 inform.c
第1行调用C99标准,第2行调用GCC接受C11之前的草案标准,第3行调用GCC接受的C11标准版本。Clang编译器在这一点上用法与GCC相同。
1.8.4 Linux系统
Linux是一个开源、流行、类似于UNIX的操作系统,可在不同平台(包括PC和Mac)上运行。在Linux中准备C程序与在UNIX系统中几乎一样,不同的是要使用GNU提供的GCC公共域C编译器。编译命令类似于:
gcc inform.c
注意,在安装Linux时,可选择是否安装GCC。如果之前没有安装GCC,则必须安装。通常,安装过程会将cc作为gcc的别名,因此可以在命令行中使用cc来代替gcc。
欲详细了解GCC和最新发布的版本,请访问http://www.gnu.org/software/gcc/index.html。
1.8.5 PC的命令行编译器
C编译器不是标准Windows软件包的一部分,因此需要从别处获取并安装C编译器。可以从互联网免费下载Cygwin和MinGW,这样便可在PC上通过命令行使用GCC编译器。Cygwin在自己的视窗运行,模仿Linux命令行环境,有一行命令提示。MinGW在Windows的命令提示模式中运行。这和GCC的最新版本一样,支持C99和C11最新的一些功能。Borland的C++编译器5.5也可以免费下载,支持C90。
源代码文件应该是文本文件,不是字处理器文件(字处理器文件包含许多额外的信息,如字体和格式等)。因此,要使用文本编辑器(如,Windows Notepad)来编辑源代码。如果使用字处理器,要以文本模式另存文件。源代码文件的扩展名应该是.c。一些字处理器会为文本文件自动添加.txt扩展名。如果出现这种情况,要更改文件名,把txt替换成c。
通常,C编译器生成的中间目标代码文件的扩展名是.obj(也可能是其他扩展名)。与UNIX编译器不同,这些编译器在完成编译后通常不会删除这些中间文件。有些编译器生成带.asm扩展名的汇编语言文件,而有些编译器则使用自己特有的格式。
一些编译器在编译后会自动运行链接器,另一些要求用户手动运行链接器。在可执行文件中链接的结果是,在原始的源代码基本名后面加上.exe扩展名。例如,编译和链接concrete.c源代码文件,生成的是concrete.exe文件。可以在命令行输入基本名来运行该程序:
C>concrete
1.8.6 集成开发环境(Windows)
许多供应商(包括微软、Embarcadero、Digital Mars)都提供Windows下的集成开发环境,或称为IDE(目前,大多数IDE都是C和C++结合的编译器)。可以免费下载的IDE有Microsoft Visual Studio Express和Pelles C。利用集成开发环境可以快速开发C程序。关键是,这些IDE都内置了用于编写C程序的编辑器。这类集成开发环境都提供了各种菜单(如,命名、保存源代码文件、编译程序、运行程序等),用户不用离开IDE就能顺利编写、编译和运行程序。如果编译器发现错误,会返回编辑器中,标出有错误的行号,并简单描述情况。
初次接触Windows IDE可能会望而生畏,因为它提供了多种目标(target),即运行程序的多种环境。例如,IDE提供了32位Windows程序、64位Windows程序、动态链接库文件(DLL)等。许多目标都涉及Windows图形界面。要管理这些(及其他)选择,通常要先创建一个项目(project),以便稍后在其中添加待使用的源代码文件名。不同的产品具体步骤不同。一般而言,首先使用【文件】菜单或【项目】菜单创建一个项目。选择正确的项目形式非常重要。本书中的例子都是一般示例,针对在简单的命令行环境中运行而设计。Windows IDE提供多种选择以满足用户的不同需求。例如,Microsoft Visual Studio提供【Win32控制台应用程序】选项。对于其他系统,查找一个诸如【DOS EXE】、【Console】或【Character Mode】的可执行选项。选择这些模式后,将在一个类控制台窗口中运行可执行程序。选择好正确的项目类型后,使用IDE的菜单打开一个新的源代码文件。对于大多数产品而言,使用【文件】菜单就能完成。你可能需要其他步骤将源文件添加到项目中。
通常,Windows IDE既可处理C也可处理C++,因此要指定待处理的程序是C还是C++。有些产品用项目类型来区分两者,有些产品(如,Microsoft Visual C++)用.c文件扩展名来指明使用C而不是C++。当然,大多数C程序也可以作为C++程序运行。欲了解C和C++的区别,请参阅参考资料IX。
你可能会遇到一个问题:在程序执行完毕后,执行程序的窗口立即消失。如果不希望出现这种情况,可以让程序暂停,直到按下Enter键,窗口才消失。要实现这种效果,可以在程序的最后(return这行代码之前)添加下面一行代码:
getchar();
该行读取一次键的按下,所以程序在用户按下Enter键之前会暂停。有时根据程序的需要,可能还需要一个击键等待。这种情况下,必须用两次getchar():
getchar();
getchar();
例如,程序在最后提示用户输入体重。用户键入体重后,按下Enter键以输入数据。程序将读取体重,第1个getchar()读取Enter键,第2个getchar()会导致程序暂停,直至用户再次按下Enter键。如果你现在不知所云,没关系,在学完C输出后就会明白。到时,我们会提醒读者使用这种方法。
虽然许多IDE在使用上大体一致,但是细节上有所不同。就一个产品的系列而言,不同版本也是如此。要经过一段时间的实践,才会熟悉编译器的工作方式。必要时,还需阅读使用手册或网上教程。
Microsoft Visual Studio和C标准
在Windows软件开发中,Microsoft Visual Studio及其免费版本Microsoft Visual Studio Express都久负盛名,它们与C标准的关系也很重要。然而,微软鼓励程序员从C转向C++和C#。虽然Visual Studio支持C89/90,但是到目前为止,它只选择性地支持那些在C++新特性中能找到的C标准(如,long long类型)。而且,自2012版本起,Visual Studio不再把C作为项目类型的选项。尽管如此,本书中的绝大多数程序仍可用Visual Studio来编译。在新建项目时,选择C++选项,然后选择【Win32控制台应用程序】,在应用设置中选择【空项目】。几乎所有的C程序都能与C++程序兼容。所以,本书中的绝大多数C程序都可作为C++程序运行。或者,在选择C++选项后,将默认的源文件扩展名.cpp替换成.c,编译器便会使用C语言的规则代替C++。
1.8.7 Windows/Linux
许多Linux发行版都可以安装在Windows系统中,以创建双系统。一些存储器会为Linux系统预留空间,以便可以启动Windows或Linux。可以在Windows系统中运行Linux程序,或在Linux系统中运行Windows程序。不能通过Windows系统访问Linux文件,但是可以通过Linux系统访问Windows文档。
1.8.8 Macintosh中的C
目前,苹果免费提供Xcode开发系统下载(过去,它有时免费,有时付费)。它允许用户选择不同的编程语言,包括C语言。
Xcode凭借可处理多种编程语言的能力,可用于多平台,开发超大型的项目。但是,首先要学会如何编写简单的C程序。在Xcode 4.6中,通过【File】菜单选择【New Project】,然后选择【OS X Application Command Line Tool】,接着输入产品名并选择C类型。Xcode使用Clang或GCC C编译器来编译C代码,它以前默认使用GCC,但是现在默认使用Clang。可以设置选择使用哪一个编译器和哪一套C标准(因为许可方面的事宜,Xcode中Clang的版本比GCC的版本要新)。
UNIX系统内置Mac OS X,终端工具打开的窗口是让用户在UNIX命令行环境中运行程序。苹果在标准软件包中不提供命令行编译器,但是,如果下载了Xcode,还可以下载可选的命令行工具,这样就可以使用clang和gcc命令在命令行模式中编译。