1.1 编程语言简介前端
(1)机器语言。机器语⾔是直接经过⼗六进制数表⽰当前处理器架构的机器指令码。指令码包含了当前指令的功能(⽐如算术逻辑运算、移位、分⽀、中断、I/O等)、寄存器、⽴即数等多种元素。每种处理器架构所对应的机器码的字节长度也各不相同,有些是固定长度的(⽐如ARM、MIPS等架构),有些是可变长度的(⽐如x86架构)。程序员
(2)汇编语言。汇编语⾔(Assembly Language)经过简单的指令助记符(memonics)来表⽰对应机器指令的功能、寄存器编号、⽴即数(immediates)等元素。汇编语⾔是对机器指令的简单抽象,经过汇编器(assembler)能够将汇编语句翻译成对应的机器指令码。算法
(3)高级语言。⾼级语⾔的表达形式更为抽象且贴近咱们⽇常的语⾔表述。⽽且,⾼级语⾔⽐起汇编语⾔每每更具备表达⼒,且拥有更加丰富的语法特性,以便将程序进⾏结构化和模块化。⽐如,⾼级语⾔具备⾃定义变量标识符、⾃定义数据结构、分⽀与循环、更形象⾃然的表达式等。⾼级语⾔⼀般经过编译器(compiler)可直接将表达式翻译为对应的机器指令码;也能够将⾼级语⾔先翻译为中间语⾔(相似于汇编,但可能⽐汇编适⽤范围更⼴、更利于跨平台的字节码),最后将中间语⾔翻译为最终的机器指令码。C语言是一种高级语言。对于⾼级语⾔来讲,从表达上又可分为命令式编程语⾔(imperative programming language)和陈述型编程语⾔(declarative programming language)。命令式语⾔主要包括过程式(procedural)、结构化(structured)以及⾯向对象(object-oriented)的编程语⾔;陈述型编程语⾔主要包括函数式(functional)以及逻辑型(logical)编程语⾔。⽽C语⾔则属于结构化的命令式编程语⾔。不过如今不少命令式编程语⾔也包含了⼀些函数式编程语⾔的特征。编程
1.2 C语言的历史vim
1963年,英国剑桥大学推出了CPL(Combined Programming Langurage)语言,它是一种无类型(typeless)编程语言。1967年,剑桥大学的Matin Richards在CPL的基础上开发出了BCPL(Basic Combined Programming Language)语言。1969年,美国贝尔实验室的计算机科学家肯·汤普逊(Ken Thompson)在丹尼斯·利奇(Dennis Ritchie)的支持下设计出了B语言。1969年到1972年,Dennis Ritchie为了从新实现Unix操做系统,在B语言的基础上最终设计出了一种新的语言,他取了BCPL的第二个字母做为这种语言的名字,这就是C语言。后端
1.3 C语言的发展数组
1989年,美国国家标准协会(ANSI)对C语言进行了标准化,这就是ANSI C。1990年,国际标准化组织(ISO)采纳了ANSI C,标准号为ISO/IEC 9899: 1990,这就是ISO C。ANSI C和ISO C是彻底相同的标准,既能够称为C89,又能够称为C90。安全
1994年,ANSI/ISO联合委员会开始着手修订C标准。1999年,这个委员会正式发布了ISO/IEC 9899:1999标准,即C99标准。C99标准引入了许多特性,包括内联函数(inline functions)、可变长度的数组、灵活的数组成员(用于结构体)、复合字面量、指定成员的初始化器、对IEEE754浮点数的改进、支持不定参数个数的宏定义,在数据类型上还增长了long long int以及复数类型。委员会的目标还不是添加新特性,而是为了达到新的目标。第一个目标是,支持国际化编程,例如提供多种方法处理国际字符集。第二个目标是,“调整现有实践致力于解决明显的缺陷”,所以,在遇到须要将C移植至64位处理器时,委员会根据现实须要来添加标准。第三个目标是,为适应科学和工程项目中的关键数字计算,提升C的适应性,让C比Fortran更有竞争力。虽然C99标准已经发布了很长时间,但并不是全部的编译器都彻底实现了C99标准的内容,即使是GCC和Clang编译器也只支持到90%以上,而微软的Visual Studio中的C编译器只能支持到70%左右。服务器
2007年,C语言标准委员会又开始修订C语言标准,直到2011年正式发布了ISO/IEC 9899:2011,即C11标准。C11标准新引入的特征尽管没C99相对C90引入的那么多,可是这些也都十分有用,好比:字节对齐说明符、泛型机制(generic selection)、对多线程的支持、静态断言、原子操做以及对Unicode的支持。这次,委员会提出了一些新的指导原则。出于对当前编程安全的担心,不那么强调“信任程序员”的目标了。并且,供应商并未像C90那样很好地接受和支持C99,这使得C99的一些特性成为C11的可选项。由于委员会认为,不该要求服务小型机市场的供应商支持其目标环境中用不到的特性。另外须要强调的是,修订标准的缘由不是由于原标准不能用,而是须要跟进新的技术。例如,新标准添加了可选项支持当前多处理器的计算机。数据结构
1.4 为何要使用C语言
在过去40多年里,C语言已经成为最重要、最流行的编程语言之一。它的成长归功于使用过的人都对它很满意。过去20多年里,虽然出现了不少新兴的编程语言(例如C++、Objective C、Java、Go、Swift、Rust等),可是C语言仍凭借自身实力作到了屹立不倒,在最新的世界编程语言排行榜(TOIBE)上,C语言仍然是首屈一指的编程语言。
使用C语言的理由:
(1)可移植性。C是可移植的语言。这意味着,在一种系统中编写的C程序稍做修改甚至不用修改就能在其它系统运行。C语言被设计出来的一大初衷就是为了能将同一个源代码放到各个不一样的平台运行。所以,若是咱们的代码要在多种不一样架构的处理器上运行的话,咱们就得注意C语言标准规定了哪些特性是编译器必须遵照的,哪些特性是平台或编译器本身实现的。咱们要尽可能使用标准中已明文规定的编程规范,尽量避免在不一样平台可能会产生不一样行为的语法特性。固然,因为上面提到的处理器种类太过多样,尤为在嵌入式开发领域,不少MCU用的还都是8位处理器,这种状况下C源代码就很难被移植到32位或64位系统下了。
(2)可维护性。可维护性在实际工程项目的研发中很是重要。它体如今最初工程架构的设计、对各个功能模块的划分、相应的开发人员安排,还有后期的测试。通常来讲,如今一个工程若是是从无到有进行开发的话会采用螺旋式开发模型。也就是说,一个项目启动后,能够先作一个功能简单但能正常工做的产品原型。而后在此基础上不断地为它增长更多功能,或对以前的功能进行修改。在此期间,咱们如何对整个工程进行模块化划分,从而能安排不一样开发人员针对不一样功能模块进行开发就变得尤其重要。另外,在工程开发过程当中,若是有人员流动,那么如何将即将离职的开发人员手中的工做交付给新人也关系到整个项目的进展。所以,一个良好的C语言代码应该具备可读性、良好的文档化注释风格,以及较详细的设计文档。对于一个较大的工程项目来讲,开发人员不只仅须要把本身的代码写好,并且要写得能让别人看懂,而且要作好详细的设计文档,这样才能把项目风险下降。
(3)可延展性。你们或许已经知道,像微软的Windows操做系统由数千名工程师合做研发;Linux操做系统对外开源,参与其中的研发人员也有数百上千人。若是咱们在一个开发团队中负责一个须要由多人合做开发的工程项目,那么咱们写的功能模块须要与其余人写的功能模块进行对接。因此,咱们在开发一个较大工程项目时,须要协调好各自对外的模块接口(Application Program Interface,API)。因为C语言没有全局名字空间(namespace)这个概念,因此命名一个对外接又也是很是重要的,不然可能会与其余功能模块的接又名发生冲突。
(4)性能。性能是提高程序使用者效率和生产力的体现。一个应用程序的性能越高,那么计算一个任务所花费的时间越短,也越节省计算机的耗电。而对于如何提高性能,一方面须要程序员对处理器架构、硬件特性有必定了解;另外一方面须要程序员拥有比较丰富的算法知识,能针对实际需求灵活采用高效的算法。而像C语言这种十分接近硬件底层的高级编程语言,能极大限度地发挥处理器的特长,从而达到高效的运行性能。
1.5 C语言的缺点
人无完人,金无足赤。C语言也有一些缺点。例如,要享受用C语言自由编程的乐趣,就必须承担更多的责任。特别是,C语言使用指针,而涉及指针的编程错误每每难以察觉。有句话说的好:想拥有自由就必须时刻保持警戒。
1.6 C语言的应用范围
虽然这些年来C++和JAVA很是流行,可是C语言还是软件业中的核心技能。在最想具有的技能中,C语言一般位居前十。特别是,C语言已成为嵌入式系统编程的流行语言。也就是说,愈来愈多的汽车、照相机、DVD播放机和其余现代化设备的微处理器都用C语言进行编程。除此以外,C语言还从长期被FORTRAN独占的科学编程领域分得一杯羹。最终,做为开发操做系统的卓越语言,C在Linux开发中扮演着极其重要的角色。所以,在进入21世纪的第2个10年中,C语言仍然保持着强劲的势头。
1.7 主流C语言编译器介绍
对于当前主流桌面操做系统而言,可以使用Visual C++、GCC以及LLVM Clang这三大编译器。其中,Visual C++(简称MSVC)只能用于Windows操做系统;其他两个,除了可用于Windows操做系统以外,主要用于Unix/Linux操做系统。像如今不少版本的Linux都默认使用GCC做为C语言编译器。而像FreeBSD、macOS等系统默认使用LLVM Clang编译器。因为当前LLVM项目主要在Apple的主推下发展的,因此在macOS中,Clang编译器又被称为Apple LLVM编译器。MSVC编译器主要用于Windows操做系统平台下的应用程序开发,它不开源。用户可使用VisualStudio Community版原本无偿使用它,可是若是要把经过Visual Studio Community工具生成出来的应用进行商用,那么就得好好阅读一下微软的许可证和说明书了。而使用GCC与Clang编译器构建出来的应用通常没有任何限制,程序员能够将应用程序随意发布和进行商用。不过因为MSVC编译器对C99标准的支持就十分有限,加之它压根不支持任何C11标准,因此本书的代码例子不会针对MSVC进行描述。所幸的是,Visual Studio Community 2017加入了对Clang编译器的支持,官方称之为——Clang with Microsoft CodeGen,当前版本基于的是Clang 3.8。也就是说,应用于Visual Studio集成开发环境中的Clang编译器前端可支持Clang编译器的全部语法特性,然后端生成的代码则与MSVC效果同样,包括像long整数类型在64位编译模式下长度仍然为4个字节,因此各位使用的时候也须要注意。
而在嵌入式系统方面,可用的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语法扩展。
1.8 GNU规范的语法扩展
GNU是一款能用于构建类Unix操做系统的计算机软件合集,由自由软件之父Richard Stallman开创,于1983年9月27日对外发布。GNU彻底由自由软件(free software)构成。GNU语法扩展源自于GCC编译器,在1987年发布1.0版本,称为GNU C Compiler。随后,GCC编译器前端支持了C++、Objective-C/C++、Fortran、Ada、Java以及最近跃升的Go等编程语言,所以如今GCC被称为GNU Compiler Collection。因为在20世纪90年代,GNU C编译器就对C90标准作了至关多的语法扩展,包括复合字面量、匿名结构体和数组、可指定的初始化器等,这些语法扩展被普遍使用,尤为是大量用于Linux内核代码中,所以C99标准将这些语法特性全都列入标准之中。
正由于GCC自己是开源自由软件,所以不少商用编译器也基于GCC进行扩展。像ARM的RVCT(RealView Compiler Toolkit)自己就支持GNU扩展。还有很多开发平台自己就直接使用GCC编译工具。因为有很多大公司顶级开发人员的参与,所以GCC编译器的目标代码优化能力至关高,并且还支持许多不一样的处理器。因此,GCC当前被普遍使用并博得开发者的好评。像Linux操做系统基本默认使用GCC做为默认编译器,包括Android的NDK开发工具一开始也是如此。
然而,因为GCC基于比较严格的GPL许可证,许多大型商业开发商对它望而却步。该许可证容许使用者无偿使用软件,可是要求不能随意对它进行篡改并从新发布。若是开发者对它进行篡改,而后发布本身修改以后的软件,那么必需要把本身修改的那部分也开源出来。所以,在2003年诞生了一个LLVM开源项目,基于更为宽松的BSD许可证,其编译器称为Clang。BSD许可证容许开发者随意对软件进行修改并从新发布,甚至能够将修改过的版本做为自主版权,于是这个许可证深受大公司的欢迎。如今Apple对LLVM项目的投入很是大。macOS上的开发工具Xocde从4.0版本起就开始使用Clang编译工具链,随后Apple将本身改写的Clang编译器称为Apple LLVM。当前最新的Xcode 8所使用的Apple LLVM版本为8.x。而当前Android NDK也支持了Clang编译器工具链。Clang编译器并不是基于GCC,它是从头开始写的。可是它的目标是尽可能与GCC编译器兼容,因此Clang编译器包含大部分GNU语法扩展,除此以外还含有它本身特有的C语言扩展。固然也有一些特性是GCC含有而Clang不具有的,不过这些特性通常不多使用。
咱们如今能够看到GNU语法扩展适用性十分普遍。若是读者当前在作Linux/Unix或Windows上的C语言编程开发,或者是在开发macOS/iOS应用,又或者是在开发Android应用,那么彻底能够毫无顾忌地使用GNU语法扩展。Clang编译器已经包含了大部分GNU语法扩展,所以在介绍GCC语法扩展的时候,若是当前特性Clang不支持,则会指明。
1.9 使用C语言的流程步骤
C是编译型语言。若是以前使用过编译型语言(如,Pascal、Fortran、C++、Go、Rust),就会很熟悉组建C程序的几个基本步骤。可是,若是之前使用的是解释型语言(如,Basic、JavaScript、Java、C#、Python、Ruby)或面向图形界面语言(如,Visual Basic),或者甚至没接触过任何编程语言,就有必要学习如何编译。
C语言编程通常分为7步:
第1步:定义程序的目标。
在动手写程序以前,要在脑中有清晰的思路。想要程序去作什么首先本身要明确本身想作什么,思考你的程序须要哪些信息,要进行哪些计算和控制,以及程序应该要报告什么信息。在这一步骤中,不涉及具体的计算机语言,应该用通常术语来描述问题。
第2步:设计程序。
对程序应该完成什么任务有概念性的认识后,就应该考虑如何用程序来完成它。例如,用户界面应该是怎样的?如何组织程序?目标用户是谁?准备花多长时间来完成这个程序?
除此以外,还要决定在程序(还多是辅助文件)中如何表示数据,以及用什么方法处理数据。学习C语言之初,遇到的问题都很简单,没什么可选的。可是,随着要处理的状况愈来愈复杂,须要决策和考虑的方面也愈来愈多。一般,选择一个合适的方式表示信息能够更容易地设计程序和处理数据。
再次强调,应该用通常术语来描述问题,而不是用具体的代码。可是,你的某些决策可能取决于语言的特性。例如,在数据表示方面,C的程序员就比Pascal的程序员有更多选择。
第3步:编写代码。
设计好程序后,就能够编写代码来实现它。也就是说,把你设计的程序翻译成C语言。这里是真正须要使用C语言的地方。能够把思路写在纸上,可是最终仍是要把代码输入计算机。这个过程的机制取决于编程环境,咱们稍后会详细介绍一些常见的环境。通常而言,使用文本编辑器建立源代码文件。该文件中内容就是你翻译的C语言代码。程序清单1.1是一个C源代码的示例。
程序清单1.1 C源代码示例
#include <stdio.h> int main(void) { int cats; printf("How many cats do you have?\n"); scanf("%d", &cats); printf("So you have %d cat(s)!\n", cats); return 0; }
在这一步骤中,应该给本身编写的程序添加文字注释。最简单的方式是使用C的注释工具在源代码中加入对代码的解释。
第4步:编译。
接下来的这一步是编译源代码。再次提醒读者注意,编译的细节取决于编程的环境,咱们稍后立刻介绍一些常见的编程环境。如今,先从概念的角度讲解编译发生了什么事情。
前面介绍过,编译器是把源代码转换成可执行代码的程序。可执行代码是用计算机的机器语言表示的代码。这种语言由数字码表示的指令组成。如前所述,不一样的计算机使用不一样的机器语言方案。C编译器负责把C代码翻译成特定的机器语言。此外,C编译器还将源代码与C库(库中包含大量的标准函数供用户使用,如printf()和scanf())的代码合并成最终的程序(更精确地说,应该是由一个被称为连接器的程序来连接库函数,可是在大多数系统中,编译器运行连接器)。其结果是,生成一个用户能够运行的可执行文件,其中包含着计算机能理解的代码。
编译器还会检查C语言程序是否有效。若是C编译器发现错误,就不生成可执行文件并报错。理解特定编译器报告的错误或警告信息是程序员要掌握的另外一项技能。
第5步:运行程序。
传统上,可执行文件是可运行的程序。在常见环境(包括Windows命令提示符模式、UNIX终端模式和Linux终端模式)中运行程序要输入可执行文件的文件名,而其余环境可能要运行命令(如,在VAX中的VMS[2])或一些其余机制。例如,在Windows和Macintosh提供的集成开发环境(IDE)中,用户能够在IDE中经过选择菜单中的选项或按下特殊键来编辑和执行C程序。最终生成的程序可经过单击或双击文件名或图标直接在操做系统中运行。
第6步:测试和调试程序。
程序能运行是个好迹象,但有时也可能会出现运行错误。接下来,应该检查程序是否按照你所设计的思路运行。你会发现你的程序中有一些错误,计算机行话叫做bug。查找并修复程序错误的过程叫调试。学习的过程当中不可避免会犯错,学习编程也是如此。所以,当你把所学的知识应用于编程时,最好为本身会犯错作好心理准备。随着你愈来愈老练,你所写的程序中的错误也会愈来愈不易察觉。
未来犯错的机会不少。你可能会犯基本的设计错误,可能错误地实现了一个好想法,可能忽视了输入检查致使程序瘫痪,可能会把圆括号放错地方,可能误用C语言或打错字,等等。把你未来犯错的地方列出来,这份错误列表应该会很长。
看到这里你可能会有些绝望,可是状况没那么糟。如今的编译器会捕获许多错误,并且本身也能够找到编译器未发现的错误。在学习本书的过程当中,咱们会给读者提供一些调试的建议。
第7步:维护和修改代码。
建立完程序后,你发现程序有错,或者想扩展程序的用途,这时就要修改程序。例如,用户输入以Zz开头的姓名时程序出现错误、你想到了一个更好的解决方案、想添加一个更好的新特性,或者要修改程序使其能在不一样的计算机系统中运行,等等。若是在编写程序时清楚地作了注释并采用了合理的设计方案,这些事情都很简单。
1.10 C语言程序的组成
C编程的基本策略是,用程序把源代码文件转换为可执行文件(其中包含可直接运行的机器语言代码)。典型的C实现经过编译和连接两个步骤来完成这一过程。编译器把源代码转换成中间代码,连接器把中间代码和其余代码合并,生成可执行文件。C使用这种分而治之的方法方便对程序进行模块化,能够独立编译单独的模块,稍后再用连接器合并已编译的模块。经过这种方式,若是只更改某个模块,没必要所以从新编译其余模块。另外,连接器还将你编写的程序和预编译的库代码合并。
中间文件有多种形式。咱们在这里描述的是最广泛的一种形式,即把源代码转换为机器语言代码,并把结果放在目标代码文件(或简称目标文件)中(这里假设源代码只有一个文件)。虽然目标文件中包含机器语言代码,可是并不能直接运行该文件。由于目标文件中储存的是编译器翻译的源代码,这还不是一个完整的程序。
目标代码文件缺失启动代码(startup code)。启动代码充当着程序和操做系统之间的接口。例如,能够在MS Windows或Linux系统下运行IBM PC兼容机。这两种状况所使用的硬件相同,因此目标代码相同,可是Windows和Linux所需的启动代码不一样,由于这些系统处理程序的方式不一样。
目标代码还缺乏库函数。几乎全部的C程序都要使用C标准库中的函数。例如,concrete.c中就使用了printf()函数。目标代码文件并不包含该函数的代码,它只包含了使用printf()函数的指令。printf()函数真正的代码储存在另外一个被称为库的文件中。库文件中有许多函数的目标代码。
连接器的做用是,把你编写的目标代码、系统的标准启动代码和库代码这3部分合并成一个文件,便可执行文件。对于库代码,连接器只会把程序中要用到的库函数代码提取出来。
1.11 集成开发环境(IDE)和编辑器
集成开发环境(IDE)提供了一整套的工具链,提供了从编写代码到编译程序和调试程序一条龙服务。而编辑器只提供了编写代码的功能,编译程序和调试程序要在命令行(也称终端)来作。
你们可能会问,既然有了如此方便的集成开发环境,为何还要学习编辑器和命令行呢?缘由有二:
(1)熟悉命令行是一项必要的技能。不要过于依赖IDE,在Linux/Unix这类操做系统上尤为如此。诚然,咱们有Visual Studio、Eclipse、IDEA、NetBeans等方便的IDE能够用,可是,对于C语言编程的内部原理咱们也须要了解,编辑器和命令行工具能为咱们揭示其中的奥秘。编辑器分为基于图形用户界面(GUI)的编辑器和基于命令行(终端)的编辑器。好比,Windows上的记事本(notepad)和macOS上的文本编辑和Linux上的gedit就是一些具备基本功能的,基于图形用户界面的文本编辑器(固然,还有许多专门为编程而开发的文本编辑器)。基于命令行(终端)界面的编辑器有macOS和Linux上的nano/pico,固然,若是你愿意花更多的时间去学习vi/vim,也是不错的。在macOS/Linux上,C语言编译的任务用clang/gcc来完成,调试的任务用lldb/gdb来完成,这些都是基于命令行的工具。
(2)命令行适合于远程开发和调试。好比,有一个程序的源代码须要(远程)修改,你总不能说,等我开个远程桌面,打开Visual Studio来修改吧,固然这样是能够的,可是对于服务器来讲,太消耗资源了,效率也很低下,并且,不少服务器的操做系统根本就没有图形界面。这时候,开个ssh,使用命令行工具编辑、编译并调试就是理所固然的事了。
1.12 本书使用的开发环境
操做系统是64位的Ubuntu Linux 18.10,编译器是Clang 7.0或GCC 8.2,集成开发环境使用的是安装了C/C++开发插件的Eclispe,固然,你能够直接使用Eclipse C/C++开发套件。