18.1 C编程语言

C语言是Linux编程语言事实上的标准,所以为了在Linux上编写可移植的C语言程序,我们需要了解一些C语言的起源,它是如何发展的,而更重要的是如何检查程序来保持和标准的一致。

18.1.1 发展历史简介

那些对历史不感兴趣的读者无需担心,因为本书是介绍编程,而不是讲述历史,所以我们只是简单介绍C语言的发展历史。

C编程语言诞生于20世纪70年代,它部分基于早先的编程语言BCPL,并对B语言进行了扩展。Dennis M. Ritchie在1974年为该语言写了一个参考手册,同一时间,对PDP-11机器上的UNIX内核的改写也是以C语言为基础的。1978年,Brian W. Kemighan和Ritchie写了一本经典的C语言参考书籍《C编程语言》(C Programming Language),其后该书又针对C语言的改进做了更新,直至今日,这本书还在不断地重印出版。

C语言如此快速地流行起来,毫无疑问这里面有部分原因应归功于UNIX用户的快速增加,但也与其自身强大的功能和清晰的语法分不开。C语言的语法根据开发者的共识也在不断发展,但既然它与原先参考书籍中所描述的语言分歧越来越大,很明显我们需要一个标准,它既符合当前的应用,又更加精确。

1983年,ANSI建立了X3J11标准委员会来开发一个清晰、简明的C语言定义。在开发的过程中,他们对C语言做了稍许的改进,特别是增加了一个非常受欢迎的功能——声明参数类型,但总的来说,委员会只对构成C语言常见用法的已有定义做了阐明和合理化。这个标准最终在1989年发表了,它被称为ANSI C编程语言标准X3.159-1989,或简称为C89,有时又被称为C90(后者后来成为ISO C编程语言标准ISO/IEC 9899:1990。这两个标准在技术上是相同的)。

如同大多数标准一样,这个标准的发表并未结束委员会的工作,他们继续努力以阐明在规范中发现的小的差异。1993年,委员会又开始制定下一个版本的标准,即C9X。同时,他们还针对当前的标准陆续在1994、1995和1996年发表了小的修正和更新。

这个标准的最新版本出现在20世纪90年代,它正式成为C99标准并被ISO采纳,成为ISO/IEC 9899:1999。目前还有一个工作委员会J11在继续进行C语言及其函数库的标准化研究,但它现在是在国际委员会下为信息技术标准组工作。你可以通过网址http://j11.incits.org/找到更多有关当前C语言标准化工作的信息。

18.1.2 GNU编译器集

开发了Emacs编辑器(是的,我们爱Emacs)后,GNU项目的下一个主要成就(正如我们在第1章讨论的)是一个完全免费的C语言编译器gcc,它的第一个正式版本发表于1987年。

gcc最初只是一个GNU C语言的编译器,但由于目前该编译器的基本框架已支持C++、Object-C、FORTRAN、Java和Ada等许多其他编程语言及其函数库,所以对gcc的定义被修改为更合适的GNU编译器集。

gcc始终是,并且看来以后也将会是Linux上的标准编译器,并且C或C++语言也是Linux上程序设计的基本语言。更多信息可参见gcc的主页http://gcc.gnu.org。

GNUC语言编译器总是非常好地保持与C语言标准开发进度的一致,同时它也允许一些扩展功能,并且不可避免地像所有其他编译器一样,在标准正式推出和编译器完全实现该标准之间有稍微的延迟。但有时也会出现相反的情况,gcc期望标准能稍作一些修改,这一点也让人非常困惑。gcc包含许多命令行命令和选项,它们允许你指定希望gcc遵守的C语言标准版本,以及控制编译器审查程序语法时的严格程度。

18.1.3 gcc选项

在了解了一些C语言标准发展的背景之后,现在我们来查看gcc编译器提供的一些选项,它们可以用来确保我们所编写的C语言程序是完全遵守该语言的标准的。我们可以用3种方法来确保编写的C语言代码不仅遵守标准,而且还是代码清晰的。它们是:用可以控制标准版本的选项来指定我们期望代码兼容的标准版本;定义用来控制头文件的常量;用警告选项对代码进行更严格的检查。

gcc编译器包含有大量的选项,在这里,我们将只介绍那些最重要的选项。完整的选项列表可以在gcc手册页中找到。我们还将简单介绍一些可以使用的#define选项,它们通常必须在源代码中的任何#include语句之前设置或在gcc命令行上定义。你可能会感到惊讶,为什么需要用这么多选项来选择一个要使用的标准,而不能只用一个标记来强制使用当前的标准呢?原因是,由于许多以前的程序依赖编译器的历史行为,如果要将它们全部更新到遵守最新的标准,我们需要付出巨大的努力,并且我们并不希望编译器升级以后就不再支持以前可以正常工作的代码,而且随着标准的发展,我们希望编译器能够针对特定的标准正常工作,即使它并不是最新版本的标准。

即使仅仅是为个人使用而编写一个小的程序,在这种情况下,虽然让程序遵守标准显得并不是那么重要,但仍然值得在编译时启用更多的gcc警告选项,因为这样可以让编译器在真正运行程序之前找出程序代码中的错误。与使用调试器以步进的方式来查找代码问题相比,使用这种方式更有效率。编译器包括很多选项,它们的功能不仅仅只是检查代码是否遵守标准的规定,而且还可以检查出虽然遵守标准但可能包含歧义的代码。例如,代码中可能存在一个执行序列,它将允许变量在未初始化之前就被访问。

如果确实需要将编写的代码与他人分享,除了在编译时选择需要遵守的标准版本和合适的警告选项外,还有非常重要的一点是,要努力确保你的代码在编译时没有任何警告信息出现。如果你在编译时允许出现一些警告信息并且养成习惯忽略它们,那么当有一天在编译时出现非常严重的警告信息时,你也可能会把它忽略。如果代码在编译时永远都保持整洁,那么当出现新的警告信息时,它就会显得非常明显。我们应该养成保持编译代码整洁的习惯。

1.控制标准版本的编译选项

这些选项在命令行上传递给gcc。我们只在下面讲解那些最重要的选项。

❑ -ansi:这是最重要的标准选项,它告诉编译器遵守C语言的ISO C90标准。它关闭那些与标准不兼容的gcc扩展,禁用C语言程序中的C++(//)风格注释,并启用ANSI的三字母词(trigraph)特性。同时通过定义宏STRICT_ANSI来关闭在头文件中与标准不兼容的一些gcc扩展。未来的编译器版本可能会修改这个选项指向的C语言标准。

❑ -std=:通过这个选项可以对使用的标准进行更精细地控制,它通过使用一个参数来设置需要的标准。其主要的选项如下所示。

■ c89:支持C89标准。

■ iso9899:1999:支持最新的ISO C90标准。

■ gnu89:支持C89标准,但同时支持GNU的扩展和一些C99特性。对于gcc的4.2版本来说,这是默认行为。

2.控制标准版本的常量

这些常量(#define)可以通过编译器的命令行选项来设置,或者通过源代码中的#define语句来定义。我们通常建议用前者设置这些常量。

❑ STRICT_ANTI:强制使用C语言的ISO标准。这个常量在使用编译器的命令行选项-ansi时被定义。

❑ _P0SIX_C_S0URCE=2:启用由IEEE Std 1003.1和1003.2标准定义的特性。我们还会在本章后面的内容中谈到这些标准。

❑ _BSD_SOURCE:启用BSD类型的特性。如果这些特性与POSIX定义冲突,则以BSD的定义为准。

❑ _GNU_SOURCE:启用大量特性,其中包括GNU扩展。如果这些特性与POSIX定义冲突,则以POSIX定义为准。

3.编译器的警告选项

这些选项在编译器的命令行上传递。我们在下面只列出主要的选项。完整的选项列表可以在gcc的手册页中找到。

❑ -pedantic:这是用于检查C语言代码的功能最强大的编译器选项。它除了启用用于检查代码是否遵守C语言标准的选项外,还关闭了一些不被标准允许的传统C语言结构,并且禁用所有的GNU扩展。如果你希望代码能够尽可能地做到可移植,就需要使用这个选项。这个选项的缺点是,它在检查代码时显得非常挑剔,有时你不得不非常仔细地思考,以去除那些最后的警告信息。

❑ -Wformat:检查printf系列函数所使用的参数类型是否正确。

❑ -Wparentheses:检查是否总是提供了需要的圆括号,即使在某些环境中并不是必须要使用它们。当想要检查对一个复杂结构的初始化是否按照预期进行时,这个选项就很有用。

❑ -Wswitch-default:检查是否所有的switch语句都包含一个default case,这通常是一个好的编码习惯。

❑ -Wunused:检查诸如声明静态函数但没有定义、未使用的参数和丢弃返回结果等情况。

❑ -Wall:启用绝大多数gcc的警告选项,包括所有以-W为前缀的选项(不包括选项-pedantic),这个选项对保持代码的整洁很有用。

gcc还包括许多警告选项,详细情况请阅读gcc的网页。一般来说,我们建议使用选项-Wall,它在检查代码质量和不让编译器生成太多的琐碎警告之间达到了很好的平衡,因为要清除掉这些琐碎的警告需要耗费程序员太多的精力。