C--C语言概述

一、C语言的发展过程

1、C语言的产生

C程序设计语言最早是由Dennis M.Ritchie于1969年到1973年在AT&T贝尔实验室⾥开发出来,主要⽤于重新实现Unix操作系统。此时,C语⾔又被称为K&R C。其中,K表⽰Kernighan的⾸字母,⽽R则是Ritchie的⾸字母。K&R C语⾔与后来标准化的C语⾔有很⼤差异。当时的第⼀本由 B.W.Kernighan 和 D.M.Ritchit 合著的著名的C语⾔专业书《The C Programming Language》也并⾮⼀个正式的编程语⾔规范,但被⽤了许多年。

2、C90标准

由于C语⾔被各⼤公司所使⽤(包括当时处于⿍盛时期的IBM PC),因此到了1988年,C语⾔由美国国家标准协会(ANSI)进⾏了标准化,此时C语⾔又被称为ANSI C。⽽到了1990,ANSI C就被国际标准化组织ISO给采纳了。此时,C语⾔在ISO中有了⼀个官⽅名称——ISO/IEC 9899:1990。其中,9899是C语⾔在ISO标准中的代号,像C++在ISO标准中的代号是14882。⽽冒号后⾯的1990表⽰当前修订好的版本是在1990年发布的。对于ISO/IEC 9899:1990的俗称或简称,有些地⽅称为C89,有些地⽅称为C90,或者C89/90。不管怎么称呼,它们都指代这个最初的C语⾔国际标准。这个版本的C语⾔标准作为K&R C的⼀个超集(即K&R C是此标准C的⼀个⼦集),把后来引⼊的许多⾮官⽅特性也⼀起整合了进去。其中包括了从C++借鉴的函数原型(Function Prototypes),指向void的指针,对国际字符集以及本地语⾔环境的⽀持。在此标准中,尽管已经将函数定义的⽅式改为现在我们常⽤的那种⽅式,不过K&R的语法形式仍然兼容。

3、C99标准

在随后的⼏年⾥,C语⾔的标准化委员会又不断地对C语⾔进⾏改进,到了1999年,正式发布了ISO/IEC 9899:1999,简称为C99标准。C99标准引⼊了许多特性,包括内联函数(inline functions)、可变长度的数组、灵活的数组成员(⽤于结构体)、复合字⾯量、指定成员的初始化器、对IEEE754浮点数的改进、⽀持不定参数个数的宏定义,在数据类型上还增加了long long int以及复数类型。毫不夸张地说,即便到⽬前为⽌,很少有C语⾔编译器是完整⽀持C99的。像主流的GCC以及Clang编译器都能⽀持⾼达90%以上,⽽微软的Visual Studio 2015中的C编译器只能⽀持到70%左右。

4、C11标准

2007年,C语⾔标准委员会又重新开始修订C语⾔,到了2011年正式发布了ISO/IEC 9899:2011简称为C11标准。C11标准新引⼊的特征尽管没C99相对C90引⼊的那么多,但是这些也都⼗分有⽤,⽐如:字节对齐说明符、泛型机制(generic selection)、对多线程的⽀持、静态断⾔、原⼦操作以及对Unicode的⽀持。本书将主要针对C11标准为⼤家详细讲解C编程语⾔。关于C语⾔历史与演化进程的详细介绍可参考维基百科:链接。

 

二、C语言编写程序注意事项

1、可移植性

C语⾔被设计出来的⼀⼤初衷就是为了能将同⼀个源代码放到各个不同的平台上编译运⾏。因此,如果我们的代码要在多种不同
架构的处理器上运⾏的话,我们就得注意C语⾔标准规定了哪些特性是编译器必须遵守的,哪些特性是平台或编译器⾃⼰实现的。我们要尽量使⽤标准中已明⽂规定的编程规范,尽可能避免在不同平台可能会产⽣不同⾏为的语法特性。当然,由于上⾯提到的处理器种类太过多样,尤其在嵌⼊式开发领域,很多MCU⽤的还都是8位处理器,这种情况下C源代码就很难被移植到32位或64位系统下了。

2、可维护性

可维护性在实际⼯程项⽬的研发中⾮常重要它体现在最初⼯程架构的设计、对各个功能模块的划分、相应的开发⼈员安排,还
有后期的测试
。⼀般来说,现在⼀个⼯程如果是从⽆到有进⾏开发的话采⽤螺旋式开发模型。也就是说,⼀个项⽬启动后,可以先做⼀个功能简单但能正常⼯作的产品原型。然后在此基础上不断地为它增加更多功能,或对之前的功能进⾏修改。在此期间,我们如何对整个⼯程进⾏模块化划分,从⽽能安排不同开发⼈员针对不同功能模块进⾏开发就变得尤为重要。另外,在⼯程开发过程中,如果有⼈员流动,那么如何将即将离职的开发⼈员⼿中的⼯作交付给新⼈也关系到整个项⽬的进展。因此,⼀个良
好的C语⾔代码应该具有可读性、良好的⽂档化注释风格,以及较详细的设计⽂档
。对于⼀个较⼤的⼯程项⽬来说,开发⼈员不仅仅需要把⾃⼰的代码写好,⽽且要写得能让别⼈看懂,并且要做好详细的设计⽂档,这样才能把项⽬风险降低。

3、可延展性

⼤家或许已经知道,像微软的Windows操作系统由数千名⼯程师合作研发;Linux操作系统对外开源,参与其中的研发⼈员也有数百上千⼈。如果我们在⼀个开发团队中负责⼀个需要由多⼈合作开发的⼯程项⽬,那么我们写的功能模块需要与其他⼈写的功能模块进⾏对接。所以,我们在开发⼀个较⼤⼯程项⽬时,需要协调好各⾃对外的模块接口(Application Program Interface,API)。由于C语⾔没有全局名字空间(namespace)这个概念,所以命名⼀个对外接⼜也是⾮常重要的,否则可能会与其他功能模块的接⼜名发⽣冲突。本书后⾯会对C语⾔函数命名以及符号连接做进⼀步介绍。

4、性能

性能是提升程序使⽤者效率和⽣产⼒的体现。⼀个应⽤程序的性能越⾼,那么计算⼀个任务所花费的时间越短,也越节省计算机的耗电。⽽对于如何提升性能⼀⽅⾯需要程序员对处理器架构、硬件特性有⼀定了解另⼀⽅⾯需要程序员拥有⽐较丰富的算法知识,能针对实际需求灵活采⽤⾼效的算法。⽽像C语⾔这种⼗分接近硬件底层的⾼级编程语⾔,能极⼤限度地发挥处理器的特长,从⽽达到⾼效的运⾏性能。

 

三、主流C语言编译器介绍

1、对于当前主流桌⾯操作系统⽽⾔,可使⽤Visual C++GCC以及LLVM Clang三⼤编译器

2、Visual C++Microsoft Visual C++(简称Visual C++MSVCVC++VC)]只能⽤于Windows操作系统GCC以及LLVM Clang,除了可⽤于Windows操作系统之外,主要⽤于Unix/Linux操作系统。像现在很多版本的Linux都默认使⽤GCC作为C语⾔编译器。⽽像FreeBSD、macOS等系统默认使⽤LLVM Clang编译器。由于当前LLVM项⽬主要在Apple的主推下发展的,所以在macOS中,Clang编译器又被称为Apple LLVM编译器。MSVC编译器主要⽤于Windows操作系统平台下的应⽤程序开发,它不开源。⽤户可以使⽤VisualStudio Community版本来免费使⽤它,但是如果要把通过Visual StudioCommunity⼯具⽣成出来的应⽤进⾏商⽤,那么就得好好阅读⼀下微软的许可证和说明书了。⽽使⽤GCC与Clang编译器构建出来的应⽤⼀般没有任何限制,程序员可以将应⽤程序随意发布和进⾏商⽤。不过由于MSVC编译器对C99标准的⽀持就⼗分有限,加之它压根不⽀持任何C11标准,所以本书的代码例⼦不会针对MSVC进⾏描述。所幸的是,Visual StudioCommunity 2017加⼊了对Clang编译器的⽀持,官⽅称之为——Clang withMicrosoft CodeGen,当前版本基于的是Clang 3.8。也就是说,应⽤于VisualStudio集成开发环境中的Clang编译器前端可⽀持Clang编译器的所有语法特性,⽽后端⽣成的代码则与MSVC效果⼀样,包括像long整数类型在64位编译模式下长度仍然为4个字节,所以各位使⽤的时候也需要注意。为了⽅便描述,本书后⾯涉及Visual Studio集成开发环境下的Clang编译器简称为VS-Clang编译器。⽽在嵌⼊式系统⽅⾯,可⽤的C语⾔编译器就⾮常丰富了。⽐如⽤于Keil公司51系列单⽚机的Keil C51编译器;当前⼤红⼤紫的Arduino板搭载的开发套件,可⽤针对AVR微控制器的AVR GCC编译器;ARM⾃⼰出的ADS(ARM Development Suite)、RVDS(RealView Development Suite)和当前最新的DS-5 Studio;DSP设计商TI(Texas Instruments)的CCS(Code Composer Studio);DSP设计商ADI(Analog Devices,Inc.)的Visual DSP++编译器,等等。通常,⽤于嵌⼊式系统开发的编译⼯具链都没有免费版本,⽽且⼀般需要通过国内代理进⾏购买。所以,这对于个⼈开发者或者嵌⼊式系统爱好者⽽⾔是⼀道不低的门槛。不过Arduino的开发套件是可免费下载使⽤的,并且⽤它做开发板连接调试也⼗分简单。Arduino所采⽤的C编译器是基于GCC的。还有像树莓派(Raspberry Pi)这种迷你电脑可以直接使⽤GCC和Clang编译器。此外,还有像NVIDIA公司推出的Jetson TK系列开发板也可直接使⽤GCC和Clang编译器。树莓派与Jetson TK都默认安装了Linux操作系统。在嵌⼊式领域,⼀般⽐较低端的单⽚机,⽐如8位的MCU所对应的C编译器可能只⽀持C90标准,有些甚⾄连C90标准的很多特性都不⽀持。因为它们⼀⽅⾯内存⼩,ROM的容量也⼩;另⼀⽅⾯,本⾝处理器机能就⼗分有限,有些甚⾄⽆法⽀持函数指针,因为处理器本⾝不包含通过寄存器做间接过程调⽤的指令。⽽像32位处理器或DSP,⼀般都⾄少能⽀持C99标准,它们本⾝的性能也⼗分强⼤。⽽像ARM出的RVDS编译器甚⾄可⽤GNU语法扩展。

3、C语言编译器分类图

C--C语言概述_第1张图片

 

四、⽤C语⾔构建⼀个可执⾏程序的流程

1、从⽤C语⾔写源代码,然后经过编译器、连接器到最终可执⾏程序的流程图⼤致如下图所⽰。

C--C语言概述_第2张图片

2、从图中我们可以清晰地看到C语⾔编译器的⼤致流程。⾸先,我们先⽤C语⾔把源代码写好,然后交给C语⾔编译器。C语⾔编译器内部分为前端和后端。前端负责将C语⾔代码进⾏词法和语法上的解析,然后可以⽣成中间代码。中间代码这部分不是必须的,但是它能够为程序的跨平台移植带来诸多好处。⽐如,同样的⼀份C语⾔源代码在⼀台计算机上编译完之后,⽣成⼀套中间代码。然后针对不同的⽬标平台(⽐如要将这⼀套代码分别编译成ARM处理器的⼆进制机器码、MIPS处理器的⼆进制机器码以及x86处理器的⼆进制机器码),只需要编写相应⽬标平台的编译器后端即可。所以,这么做就可以把编译器的前端与后端剥离开来(这在软件⼯程上又可称为解耦合),不同处理器⼚商可以针对⾃家的处理器特性,对中间代码⽣成到⽬标⼆进制代码的过程再度进⾏优化。接下来,由C语⾔编译器后端⽣成源⽂件相应的⽬标⽂件。⽬标⽂件在Windows系统上往往是.obj⽂件;⽽在Unix/Linux系统上往往是.o⽂件。C语⾔的源⽂件在所有平台上都统⼀⽤.c⽂件表⽰。最后,对于各个独⽴的⽬标⽂件,通过连接器将它们合并成⼀个最终可执⾏⽂件。连接器与C语⾔编译器是完全独⽴的。所以,只要最终⽬标代码的ABI(应⽤程序⼆进制接⼜)⼀致,我们可以把各个编译器⽣成的⽬标代码都放在⼀起,最后连接⽣成⼀个可执⾏⽂件。⽐如,有些源代码可⽤GCC编译,有些使⽤Clang编译,还有些汇编语⾔源⽂件可直接通过汇编器⽣成⽬标代码,最后将所有这些⽣成出来的⽬标代码连接为可执⾏⽂件。最终⽤户可以在当前的操作系统上加载可执⾏⽂件进⾏执⾏。操作系统利⽤加载器将可执⾏⽂件中相关的机器码存放到内存中来执⾏应⽤程序。

 

五、C语言的工作方式

C--C语言概述_第3张图片

 

致谢

1、《C语言程序设计》[第四版],作者谭浩强

2、《C程序设计语言》[第二版],作者 Brian W.Kernighan, Dennis M.Ritchie     

3、《C语言编程魔法书》作者 陈轶

4、主流C/C++编译器|编译环境简介

 

 

 

 

你可能感兴趣的:(C,C进阶)