《编写高质量代码:改善c程序代码的125个建议》——第1章 数据,程序设计之根本建议1:认识ANSI C

简介:

本节书摘来自华章计算机《编写高质量代码:改善c程序代码的125个建议》一书中的第1章,建议1,作者:马 伟 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

第1章 数据,程序设计之根本

数据是程序设计最基础的概念,程序对数据进行操作。换句话说,任何一个完整的程序都可以看成是一组数据和作用于这组数据上的操作的说明。同时,程序中的每个数据项也都有一个与之相关的类型,称为“数据类型”。
这样,在程序中就可以使用数据类型来区分不同的数据,进而根据实际需要为这些数据分配不同的存储空间。这就像成年人必须睡成人床,而给婴儿配备婴儿床就足够了,如果你给婴儿分配一张成人床就会造成资源浪费,相反给成年人分配一张婴儿床则有可能会发生“溢出”。数据类型也一样,由于不同的数据所需要的存储容量各不相同,因此需要分配的内存空间大小也会不一样,这样才能够保证内存资源的合理配置,使程序性能达到最优化。因此,如何合理、安全地使用这些数据类型是每个程序员必须掌握的。本章将围绕这一话题进行讨论。

建议1:认识ANSI C

谈到C语言的发展历程,就不得不从最早的二进制语言说起。大家都知道,二进制语言可以说是世界上最早的计算机语言,它只允许程序设计人员使用计算机能够直接识别和执行的二进制代码(即0和1,其中,0代表低电压,1代表高电压)来编写程序。可想而知,这样的编码方式对程序设计人员来说是多么困难与枯燥。因此,为了提高程序设计效率并减轻程序设计人员的负担,所以后来很快便出现了汇编语言(Assembly Language)。
与二进制语言一样,汇编语言也是面向机器的程序设计语言,不同类型的计算机上需要提供不同的汇编语言。但与二进制语言不同的是,汇编语言使用助记符(Memoni)来代替操作码,并用地址符号(Symbol)或标号(Label)来代替地址码。由于汇编语言采用符号代替二进制代码,因此,它也被称为符号语言。使用汇编语言编写的程序机器并不能直接识别,而需要通过一种程序将汇编语言翻译成机器能够识别的二进制语言,这种起翻译作用的程序就是汇编程序。
相对于二进制语言,汇编语言不仅使开发效率得到了很大提升,而且它还具有许多优点,比如它能够直接同计算机的底层软件或硬件进行交互,直接访问与硬件相关的存储器或 I/O端口;能够不受编译器的限制,对生成的二进制代码进行完全控制;能够对关键的代码进行更准确地控制,避免因线程共同访问或硬件设备共享而引起的死锁;能够根据特定的应用对代码进行最佳优化,提高运行速度等。
尽管如此,汇编语言依旧是一种层次非常低的语言,它仅仅高于直接手工编写二进制的机器指令码。在实际应用中,它仍然暴露了一些不可避免的缺陷:如编写的代码非常难以阅读,不好维护;很容易产生bug,难于调试;一般只能针对特定的体系结构和处理器进行优化;开发效率很低,时间长且单调等。因此,我们更加需要一种设计描述简单,能脱离对机型的要求,并且能在任何计算机上运行的计算机语言,我们称这种语言为高级语言。这样,程序设计人员就可以将问题及解决问题的算法过程描述出来,利用这种高级语言直接写出各种表达式来描述简单的计算过程,而无须针对不同的机型编写不同的代码。
高级语言编写的程序称为源程序,源程序不能在计算机上直接运行,必须将其翻译成二进制代码后才能执行。一般有两种翻译方式:一种是“解释程序”方式,即将源程序作为输入,翻译一句后就提交计算机执行一句,这种方式并不形成目标程序;另一种是“编译程序”方式,即将源程序作为输入,全部翻译成二进制代码后再执行,编译后的二进制程序称为目标程序。
世界上出现的第一种高级语言是Algol语言,它也可以算作C语言的前身。它和普通语言表达式非常接近,适用于数值计算,所以Algol多用于科学计算机。1960年Algol 60版本推出后,很受程序设计人员欢迎。Algol 60推出了许多新的概念,如局部性概念、动态、递归、巴科斯-诺尔范式(Backus-Naur Form,BNF)等。从某种意义上讲,Algol 60应该是程序设计语言发展史上的一个里程碑,它标志着程序设计语言已成为一门独立的科学学科,并为后来的软件自动化及软件可靠性的发展奠定了基础。
虽然使用Algol 60来描述算法很方便,但是它离计算机硬件系统却很远,不宜用来编写系统程序。1963年英国剑桥大学在Algol语言的基础上增添了处理硬件的能力,并命名为“CPL”(Combined Programming Language),即复合程序设计语言。但由于CPL的规模很大,学习和掌握都比较困难,因此没有流行。1967年剑桥大学的Martin Richards对CPL语言进行了简化,推出BCPL(Basic Combined Programming Language),即基本复合程序设计语言。它是典型的面向过程的高级语言,它的语法更加靠近机器本身,适合于开发精巧、高要求的应用程序,而且它对编译器的要求也不高。同时,BCPL也是最早使用库函数封装基本输入输出的语言之一,这使得它的跨平台可移植性很好。
1969年,美国通用电气公司、麻省理工学院与贝尔实验室联合创建了一个庞大的项目,命名为Muktics工程。该项目的目的是创建一个操作系统,不过由于该项目过于复杂和庞大,最终失败了。这也致使项目的参与者之一通用电气公司退出软件领域,同时,贝尔实验室的专家们也撤出了Muktics工程,转而研究新的领域。之后,贝尔实验室的一位名为Ken Thompson研究员和他的同事Dennis Ritchie组成一个非正式的小组,开始进行一些其他方面的研究。为了自娱自乐,Ken Thompson把他的“太空旅行”软件移植到不太常用的PDP-7系统上。与此同时,Ken Thompson还为PDP-7系统编写了一个简单的操作系统。该操作系统比起Muktics工程简单了许多,采用汇编语言编写,1970年Brian Kernighan为其取名为UNIX。
从这里可以看出,著名的操作系统UNIX是早于C语言出现的,后来才用C语言重写此系统,这一点一定要注意。
不过使用汇编语言编写程序不仅吃力而且效率低下,所以Ken Thompson就考虑利用高级语言的特性来解决这一问题。1970年,Ken Thompson进一步简化了BCPL,突出硬件的处理能力,并取“BCPL”的第一个字母“B”作为新语言的名称,即B语言。同时,他还使用B语言编写了UNIX操作系统程序。不过B语言还是存在许多问题,最大问题就在于无法表达不同的数据类型,而且效率不高,这也迫使Ken Thompson后来不得不在PDP-11的基础上重新使用汇编语言来实现UNIX。面对B语言存在的问题,1971年Dennis Ritchie通过增加类型扩展了B语言,这次采用的是编译模式而不是解释模式,并且引入了类型系统,每个变量在使用前必须声明。这种扩展的B语言称为NB,即New B的缩写。
1972年,Ken Thompson和Dennis Ritchie继续对B语言进行完善和扩充,他们在保留B语言强大硬件处理能力的基础上,扩充了数据类型,恢复了通用性,并取“BCPL”的第二个字母“C”作为新语言的名称,即C语言。其实,C语言除了增加类型系统外,它还增加了许多方便编译器设计者设计的新特性,主要表现在以下几个方面:
数组下标从0开始,而不是从1开始。例如,我们定义一个数组arr[50],因为C语言的数组下标是从0开始的,所以它的合法范围是arr[0]~arr[49]。因此,你不能够向arr[50]里存储数据。

  • 可以把数组看作指针,它简化了参数的传递方法,使大家不必忍受传递一个数组到函数时需要复制所有数组内容的低效率。不过,值得注意的是,数组与指针并非在任何情况下都是等效的,这一点会在后面进行详细阐述。
    float类型被自动扩展为double类型。虽然在ANSI C中情况不再如此,但最- 初浮点数常量的精度都是double类型的,所有表达式中float类型的变量总会被自动转化为double类型。
  • 增加register关键字,用此关键字告诉编译器设计者哪些变量被放到了寄存器中,从而简化编译器,但却也因此给程序员带来了无穷无尽的麻烦,这一点会在后面的章节中详细阐述。

此后,两人又合作重写了UNIX操作系统,C语言也伴随着UNIX操作系统成为一种广受欢迎的计算机语言。图1-1按时间顺序阐述C语言的由来。


<a href=https://yqfile.alicdn.com/97fe12ceda341080e5a3a4f8839ee8685e3e0d3a.png" >

1978年,为了让C语言脱离UNIX操作系统,成为任何计算机上都能运行的通用计算机语言,Brian Kernighan和Dennis Ritchie共同撰写了《The C Programming Language》的第1版,该著作简称为“K&R”。书中对C语言的语法进行了规范化描述,书末的参考指南则给出了当时C语言的完整定义,这也成为当时C语言事实上的标准,此标准称为“K&R C”。从此以后,C语言被移植到各种机型上,并受到广泛支持。
随着C语言在多个领域的推广和应用,一些新的特性不断被各种编译器实现并添加进来。于是建立一个新的“无歧义、与具体平台无关的C语言定义”就成为越来越重要的事情。1983年,美国国家标准委员会ANSI(American National Standards Institute)属下专门负责信息技术标准化的机构ASC X3(现已更名为国际信息技术标准委员会(International Committee for Information Technology Standards,INCITS))成立了一个专门的技术委员会 J11(J11 是委员会编号,全称是X3J11),用于起草关于C语言的标准草案。1989年,ANSI正式通过C语言标准草案,至此该标准成为美国国家标准,此标准也称为C89标准。
随后,《The C Programming Language》第2版出版发行,书中内容根据 ANSI C(C89)进行了更新。1990年,在ISO/IEC JTC1/SC22/WG14(即ISO/IEC联合技术第I委员会第22分委员会第14工作组)的努力下,ISO批准ANSI C成为国际标准,于是ISO C(又称为C90)诞生。与C89相比,C90除了标准文档的印刷编排细节有些不同外(主要表现在删除了“Rationale”一节,并把文档的格式与段落编码作了改动),它们在技术上是完全一样的。到目前为止,C89是C语言运用得最广泛的标准,基本上所有的C语言编译器都完全支持该标准。相对于“K&R C”,C89主要做了以下几方面的改进:
  • 增加了新特性——原型。原型是函数声明的扩展,它使得编译器很容易根据函数的定义检查函数的用法。
  • 增加了一些新的关键字,如enum、const、volatile、signed与void。C89的关键字见表1-1。


67bae9cd7515d706417c672a08fa7325d1e8afeb

除此之外,C89还做了许多其他的改进,如增强了预处理指令,定义了相关的宏,允许将结构本身作为参数传递给函数,从“无符号保留”转到“值保留”等。
自ISO C(C90)推出之后,ISO又于1994年与1996年分别出版了C90的技术勘误文档,更正了一些印刷错误,同时,在1995年还通过了一份C90的技术补充,这份补充对C90进行了微小扩充,扩充后的ISO C被称为C95。
1999年,ANSI和ISO又通过了最新版本的C语言标准和技术勘误文档,该标准被称为C99。这里需要说明的是,与C89不同,并非市面上所有的编译器都支持C99,并且有的编译器只支持C99的部分新特性。相对于C89,C99主要做了以下几方面的改进:

  • 增加了restrict与inline关键字。
  • 新增_Bool、_Complex与_Imaginary 3种数据类型,如C99中定义的复数类型为:float_Complex、float_Imaginary、double_Complex、double_Imaginary、long double_Complex与long double_Imaginary。
  • 增强数组的功能,支持可变长数组等。
  • 支持复合赋值。
  • 增强预处理程序,如引入_Pragma运算符,并增加了一些内部宏等。
  • 支持柔性数组结构成员,即允许结构中的最后一个元素是未知大小的数组。

由于技术的发展日新月异,因此虽然C99还没有得到完全支持,但在2007年,标准委员会就又开始起草新的C语言标准来取代现有的C99标准,该标准命名为C1X,C1X是一个非正式名字。2011年12月,ANSI正式采纳了ISO/IEC 9899:2011标准,即C11标准。相对于C99,C11主要做了如下几方面的改进:

  • 采用新的对齐规范,包括_Alignas说明符、_Alignof运算符、aligned_alloc函数与头文件。
  • 增加_Noreturn函数标记。
  • 增加_Generic关键词。
  • 增加静态断言_Static_assert()。
  • 删除gets()函数,C99中已经将此函数标记为过时,推荐新的替代函数gets_s()。
  • 采用新的fopen()模式。
  • 增加匿名结构体/联合体。
  • 支持多线程技术,包括_Thread_local与头文件。
  • 增加_Atomic类型修饰符和头文件。
  • 带边界检查(bounds-checking)的函数接口,定义了新的安全的函数,例如fopen_s()、strcat_s()等。
  • 改进Unicode支持与头文件。
  • 增加quick_exit()函数作为第三种终止程序的方式。
  • 可以创建复数的宏。
  • 增加更多处理浮点数的宏。
  • struct timespec成为time.h的一部分,以及宏TIME_UTC和函数timespec_get()。

综上所述,可以用图1-2来直观地阐述C语言标准的发展历程。


<a href=https://yqfile.alicdn.com/d050768707ba2e90d060740fbeb80d17abeb878d.png" >

在GCC编译器中,针对不同版本的C语言标准,可以通过在命令行中使用“-std”选项来选择所需要使用的C语言标准版本。
1)C89或者C90

-ansi
-std=c90
-std=iso9899:1990

2)C95

-std=iso9899:199409

3)C99

-std=c99
-std=iso9899:1999

4)C11

-std=c11
-std=iso9899:2011

5)除此之外,如果需要在GCC中使用C扩展,还可以通过如下参数形式实现:

C89或者C90:-std=gnu90
C99:-std=gnu99
C11:-std=gnu11
相关文章
|
存储 C语言
《编写高质量代码:改善c程序代码的125个建议》—— 导读
众所周知,C语言是一门既具有高级语言特点,又有汇编语言特点的通用计算机编程语言,无论是操作系统(如Microsoft Windows、Mac OS X、Linux和UNIX等)、嵌入式系统与普通应用软件,还是目前流行的移动智能设备开发,随处都可以看见它依然矫健的身影。
1884 0