版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第4章嵌入式系统软件编写4.1开发高质量软件4.3规范化编程4.2嵌入式C语言编程4.1开发高质量软件CMM将质量的定义为一个系统、组件或过程符合特定需求的程度,或定义为一个系统、组件或过程符合客户或用户的要求或期望的程度。4.1.1软件质量的基本概念示例4.1:什么是CMMCMM是软件能力成熟度模型(CapabilityMaturityModelForSoftware,简称SW-CMM/CMMI),是由美国卡内基梅隆大学软件工程研究所(CMUSEI)研究出的一种用于评价软件承包商能力并帮助改善软件质量的方法,其目的是帮助软件企业对软件工程过程进行管理和改进,增强开发与改进能力,从而能按时地、不超预算地开发出高质量的软件。CMM包括5个等级,共计18个过程域,52个目标,300多个关键实践。其中,5个成熟度等级为初始级(Initial)、可重复级(Repeatable)、已定义级(Defined)、已管理级(Managed)、持续优化级(Optimizing)。4.1.1软件质量的基本概念
我们可以将质量属性分成两大类:功能性属性与非功能性属性。如表4.1所示。我们将正确性、健壮性与可靠性列入功能性属性,其他列入非功能性属性。4.1.2软件质量的基本属性这里详细描述10类软件质量属性。1、正确性正确性是指软件按照需求正确执行任务的能力。这里“正确性”的语义涵盖了“精确性”。正确性无疑是第一重要的软件质量属性。如果软件运行不正确,将会给用户造成不便甚至损失。技术评审和测试的第一关都是检查工作成果的正确性。2、健壮性健壮性是指在异常情况下,软件能够正常运行的能力。正确性与健壮性的区别是:前者描述软件在需求范围之内的行为,而后者描述软件在需求范国之外的行为。可是正常情况与异常情况并不容易区分,开发者往往要么没想到异常情况,要么把异常情况错当成正常情况而不加以处理,结果降低了健壮性。用户才不管正确性与健壮性的区别,反正软件出了差错都是开发方的错。所以提高软件的健壮性也是开发者的义务。4.1.2软件质量的基本属性3、可靠性可靠性不同于正确性和健壮性,软件可靠性问题通常是由于设计中没有料到的异常和测试中没有暴露的代码缺陷引起的。可靠性是一个与时间相关的属性,指的是在一定环境下,在一定的时间段内,程序不出现故障的概率,因此是一个统计量,通常用平均故障时间(MTTE,mean-timetofault)来衡量。4.1.2软件质量的基本属性4、性能性能通是指软件的“时间—空间”效率,而不仅是指软性的运行速度。人们总希望软件的运行速度高些,并且占用资源少些。程序可以通过优化数据结构、算法和代码来提高软件的性能。5、易用性易用性是指用户使用软件的容易程度。现代人的生活节奏快,干什么事都想图个方便,所以把易用性作为重要的质量属性无可非议。导致软件易用性差的原因很多,从产品规划到项目管理,以及开发人员的素质、责任与意识都很重要。开发人员以为只要自己用起来方便,用户也一定会满意,有如“王婆卖瓜,自卖自夸”是不可取的。但是,软件的易用性可能受制于开发成本、开发周期等因素的影响,往往首先推出V1.0版本的软件,推出软件的关键部分,而后逐步在使用过程中完善用户体验,推出V2.0,V3.0版本。6、清晰性清晰意味着工作成果易读、易理解,这个质量属性表达了人们一种质朴的愿望:让我花钱买它或者用它,总得让我看明白它是什么东西。7、安全性这里安全性是指信息安全,英文是Security而不是Safety。安全性是指防止系统被非法入侵的能力,既属于技术问题又属于管理问题。信息安全是一门比较深奥的学问,其发展是建立在正义与的邪恶的斗争之上。这世界似乎不存在绝对安全的系统,连美国军方的系统都频频遭黑客入侵。如今全球黑客泛滥,真是“道高一尺,魔高一丈”。4.1.2软件质量的基本属性8、可扩展性可扩展性反映了软件适应“变化”的能力。在软件开发过程中,“变化”是司空见惯的事情,如需求、设计的变化,算法的改进、程序的变化等。由软件是“软”的,是否它天生就容易修改以适应“变化”?关键要看软件的规模和复杂性。如果软件规模很小,问题很简单,那么修改起来的确比较容易,这时就无所谓“可扩展性”了。要是软件的代码只有100行,那么“软件工程”也就用不着了。如果软件规模很大,问题很复杂,倘若软件的可扩展性不好,那么该软件就像用卡片造成的房子,抽出或者塞进去一张卡片都有可能使房子倒塌。可扩展性是系统设计阶段重点考虑的质量属性。9、兼容性兼容性是指两个或两个以上的软件相互交换信息的能力。由于软件不是在“真空”里应用的,它需要具备与其他软件交互的能力。例如两个字处理软件的文件格式兼容,那么它们都可以操作对方的文件,这种能力对用户很有好处。国内金山公司开发的字处理软件WPS就可以操作Word文件。10、可移植性软件的可移植性指的是软件不经修改或稍加修改就可以运行于不同软硬件环境(CPU、OS和编译器)的能力,主要体现为代码的可移植性。编程语言越低级,用它编写的程序越难移植,反之则越容易。这是因为,不同的硬件体系结构(如IntelCPU和SPARCCPU)使用不同的指令集和字长,而OS和编译器可以屏蔽这种差异,所以高级语言移植性更好。4.1.3高质量软件开发方法1、建立软件过程规范人们逐渐意识到希望顺利开发出高质量的软件产品,必须有条不紊的组织技术开发活动与开展项目管理活动。这些活动的组织形式被称为过程模型。虽然可能会存在个案,依靠能人能力的“游击战”的过程模式完成较高质量的软件开发,但谁都无法否认,软件企业应该根据产品的特征,建立一整套在企业范围内应用的有效软件开发模型或规范,并形成制度,以便组织开发与管理人员依照过程规范开展工作。软件开发模型的研究兴起于20世纪60年代末与70年代初。70年代提出了瀑布模型,同时,人们还提出了其他许多软件开发模型,比如常见的还有喷泉模型、增量模型、快速原型模型、螺旋模型、迭代模型等等。4.1.3高质量软件开发方法图4.1混合的软件开发模型4.1.3高质量软件开发方法2、复用复用是利用现成的东西。复用的对象可以是有形的物体,也可以是无形的知识成果。复用有利于提高质量,提高生产效率,并降低成本。人们总是在继承前人的成果基础上,不断加以利用、改进和创新后进步。一个新的系统可以复用成熟且经过验证的比较可靠,并具有高质量的单元模块,以保证产品的快速实现并能保证质量。软件开发过程的复用,称为软件复用。软件过程中复用函数、库、中间件等,不仅简化了软件开发的过程,减少开发工作量与代码维护代价,减低成本的同时提高效率,另一方面,软件开发过程中也要注意设计的软件本身具有复用型。即软件复用不仅要使自己拿来方便,同时也要让别人拿去方便。复用的思想在技术开发活动与项目管理活动中同样适用,比如思想方法、经验、程序、文档等等的复用。面向对象(ObjectOriented)开发有一句名言:“请不要发明相同的车轮子”,即表达复用技术在面向对象设计中的重要性。3、分治分治是把一个复杂的问题分解为若干简单的问题,然后逐一解决,即分而治之。分治思想不仅适用于生活与工作中,在技术领域同样适用。但分治说起来容易,做起来却比较困难,比如存在如何分,如何治的问题。现实情况经常会出现“硬分硬治”等诸多问题。软件中的分治,着重需要考虑将复杂问题分解后,每个问题是否能够通过程序模块实现。其次,实现后的程序模块,能否最终集成为一个有效的软件系统并解决原始的复杂问题。如图4.2所示。4.1.3高质量软件开发方法图4.2软件的分而治之策略4、优化与折中软件优化工作不是可有可无的事情,而是软件开发设计过程中必须做的事情。软件优化主要是优化软件的各个质量属性,比如提高软件的运行速度,减少对内存资源的占用,使得用户界面更加友好等等。将软件优化工作列入到项目管理工作中,成为一种项目责任,软件才能够不断的优化、完善。4.1.3高质量软件开发方法5、技术评审技术评审最初由IBM公司倡导的方法,目的是尽早地发现工作成果中的缺陷,并帮助开发人员及时消除缺陷,以有效地提高产品的质量。技术评审是业界广泛采用的对提高产品质量行之有效的方法。作为软件开发的最佳实践之一,技术评审能够在产品开发的任何阶段进行,它能够尽早的发现并消除工作成果中的缺陷,以提高产品质量,降低产品的开发成本。4.1.3高质量软件开发方法6、测试事先发现并解决问题是解决问题的最好方法。但不是所有的问题,甚至是大部分问题不是事先能够及时发现并解决。因此,测试环境不可或缺。测试的目的是发现尽可能多的缺陷。在软件开发过程中,编程与测试紧密结合。测试不是被动的进行,而应该在软件设计过程中有计划的开展测试工作。测试可以分为单元测试、集成测试、系统测试与验收测试几个阶段,一般遵循如下流程:(1)、制定测试计划。(2)、设计测试用例。(3)、执行测试。(4)、反馈问题并撰写测试报告。(5)、跟踪问题修改,并回到流程。7、质量保证质量保证是一种有计划、需要贯穿于整个产品生命周期的质量管理方法,其提供了一种有效的人员组织形式与管理形式,通过客观地检查和监控“过程质量”与“产品质量”,从而实现持续改进质量的目的。由于过程质量与产品质量存在某种因果关系,即“好的过程”可能产生“好的产品”。因此,质量保证通过有计划的检查“工作过程和工作成果”是否符合既定规范,来监控与改进质量。质量保证人员即使不是技术专家,也能通过客观的检查和监控产品质量,达到保证质量的目的。这是质量保证方法富有成效的一面。4.2嵌入式C语言编程
C语言是一门面向过程、抽象化的高级程序设计语言,保持着高级语言跨平台的特性的同时,提供了许多低级处理的功能,广泛应用于底层软件与系统软件开发。通过标准规格编写的C语言程序可在特定嵌入式处理器以及超级计算机等许多计算机平台上进行编译运行。
二十世纪八十年代,美国国家标准协会(ANSI)为了避免各开发厂商用的C语言语法产生差异,给C语言制定了一套完整的美国国家标准,被称为ANSIX3.159-1989"ProgrammingLanguageC"。因为这个标准是1989年通过的,所以一般简称C89标准或称ANSIC。作为C语言最初的标准版本,ANSIC对C语言后续版本都具有较大的影响。4.2.1C语言发展与标准4.2.1C语言发展与标准
1990年,国际标准化组织(ISO)和国际电工委员会(IEC)把C89标准定为C语言的国际标准,命名为ISO/IEC9899:1990-Programminglanguages--C。因为此标准是在1990年发布的,所以有些人把简称作C90标准。不过大多数人依然称之为C89标准,因为此标准与ANSIC89标准完全等同。
1999年,在做了一些必要的修正和完善后,ISO发布了新的C语言标准,命名为ISO/IEC9899:1999,简称“C99”。在2011年12月8日,ISO又正式发布了新的标准,称为ISO/IEC9899:2011,简称为“C11”。
GCC在不断升级维护以及影响力扩大过程中,也逐渐形成了自己的GNUC标准。GNUC除了支持ANSIC外,还对C语言进行了很多扩展,这些扩展对优化、目标代码布局、更安全地检查等方面提供了很强的支持。GNU项目始于1984年,旨在开发一个类UNIX的完整操作系统自由软件。GCC作为GNU中的一个项目,起初目的是编程开发一个开源的C语言编译器。如今的GCC变得可处理Fortran、Pascal、Objective-C、Java、Ada,以及Go等众多编程语言。在Linux下编程最常用的C编译器就是GCC。4.2.2嵌入式C语言编程
由于嵌入式系统的特定,嵌入式编程更加注重代码的质量、效率和安全性。同时,由于嵌入式体系结构的差异,嵌入式编程有许多自己的特点。本节以C语言编程为例,从内存角度理解嵌入式编程。1、大端(Big-Endian)和小端(Little-Endian)在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。大端格式是将字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中,如图4.3所示:图4.3大端格式图例4.2.2嵌入式C语言编程
小端存储格式与大端存储格式相反,将低地址中存放的是字数据的低字节,高地址存放的是字数据的高字节。如图4.4所示:图4.4小端格式图例4.2.2嵌入式C语言编程2、数据类型数据类型是一个值的集合以及定义在这个值集上的一组操作。变量是用来存储值的所在处,它们有名字和数据类型。变量的数据类型决定了如何将其代表这些值存储到计算机的内存中。所有变量都具有数据类型,以决定能够存储哪种数据。变量用于存储数据,而数据有各种类型,有的占用一个内存单元,有的占用多个内存单元。如果让程序员在定义变量时指定内存单元的个数,那是十分麻烦的。因此,在C语言中预先设置好了一些“模板”即数据类型,程序员只要指定变量的数据类型,系统就为其分配合适的存储空间。图4.5所示为C语言的数据类型,其中基本类型也称为内置数据类型。因此,在定义变量时至少需要指定变量的数据类型和变量名,可以赋初值(称为初始化),也可以不赋初值。4.2.2嵌入式C语言编程图4.5C语言数据类型4.2.2嵌入式C语言编程
在传统的VisualC++(简称VC)编程中,通常我们认为基本数据类型定义的变量存储空间是恒定的。以VC为例,六种基本数据类型及其内存占用情况如下:1)char:字符型,占一个字节2)short=shortint:短整型,占两个字节3)int:整型,占四个字节4)long=longint:长整型,占四个字节5)float:单精度浮点型,占四个字节6)double:双精度浮点型,占八个字节但在嵌入式编程中,基本数据类型及其存储空间大小可能会有差异,以面向51单片机的KeiluVision4(简称keilC51)为例,可以理解为只存在如下四种基本数据类型,其中keilC51中,int类型仅占用2个字节而非4个字节的存储空间。具体如下:1)char:字符型2)int=short=shortint:整型,占两个字节3)long=longint:长整型,占四个字节4)float=double:单精度浮点型,占四个字节4.2.2嵌入式C语言编程
当然,基本整型数据类型又可以通过signed关键词扩展为有符号的signedchar、signedint、signedlong类型,或通过unsigned关键词扩展为无符号的unsignedchar、unsignedint、unsignedlong类型;而对于不加关键词扩展的char、int、long本身,KeiluVision4则一律认为是signed类型。同时,应对51单片机硬件的一些特点,keilC51还扩展了bit、sbit、sfr、sfr16等四种特殊基本数据类型,它们都是标准C中所没有的。4.2.2嵌入式C语言编程3、变量的作用域和变量在内存中的存储方式1)局部变量和全局变量变量的作用域指变量的有效范围。在一个程序中可能定义多个变量,根据其定义位置分为局部变量和全局变量。局部变量:在函数内部定义的变量为内部变量,它只在本函数范围内有效,在该函数以外不能使用这些变量,称之为局部变量。所以局部变量的作用域仅限于定义它的函数全局变量:在函数之外定义的变量为外部变量,它的作用域为从定义变量的位置开始到本源程序文件结東,称之为全局变量。所以全局变量的作用域仅限于定义它的源程序文件,在本源程序文件的所有函数中都可以使用其中定义的全局变量。例如:4.2.2嵌入式C语言编程
上述程序输出为“10,1"。fun函数中的printf语句输出局部变量n的值10,而主函数中的printf语句输出全局变量n的值1。在相同作用域内不允许出现相同的变量名。2)变量的存储类别在定义变量时还可以指定存储类别,主要的存储类别说明符如下。auto:自动变量,在缺省情况下,编译器默认所有局部变量为自动变量,这种变量的存储空间由系统自动分配和释放,系统不会自动初始化。例如,以下程序执行时会输出n中没有意义的垃圾值,如输出“-858993460”,因为自动变量n没有初始化。例如:4.2.2嵌入式C语言编程
②register:寄存器变量,变量值存放在CPU的内部寄存器中,存取速度最快,通常只将需要频繁读的变量定义为寄存器变量,这类变量不能进行取变量地址操作(有的编译器会做优化处理,将计算量不大的寄存器变量也作为自动变量处理,如VisualC++6.0)。由于寄存器个数有限,所以一般不能在程序中定义很多寄存器变量。另外,只有局部自动变量和函数形参才可以定义为寄存器变量。③extern:外部变量,外部变量和全局变量是对同一类变量的两种不同角度的提法。全局变量是从作用域提出的,外部变量是从存储类别提出的。④static:静态变量,在函数内部用static关键字定义的变量称为静态局部变量,在函数外部用static关键字定义的变量称为静态全局变量。在程序执行期间,静态局部变量在内存的静态存储区中占据着永久性的存储单元。即使退出函数以后,下次再进入该函数时,静态局部变量仍使用原来的存储单元。对于在定义时初始化的静态局部变量,初始化仅仅执行一次:对于未初始化的静态局部变量,C编译系统自动给它赋初值0。例如:4.2.2嵌入式C语言编程
在主函数中,第1次调用fun()时,初始化静态局部变量n为0,执行n++,输出为1。第2次调用fan()时,静态局部变量n仍然存在,其值为1,执行n++,输出为2。第3次调用fun()时,输出为3。在多次调用fun()时,可以简单地理解仅仅第1次执行“staticintn;语句。在一般情况下,全局变量默认是外部的,即定义全局变量n的如下两种方式是等价的:intn=1:externintn=1;//如果不初始化。就变成声明变量n了如下方式定义的全局变量n为静态全局变量:staticintn=1;静态全局变量的作用域只限于本源程序文件。静态全局变量和普通全局变量的区别是静态全局变量只能初始化一次。以防止在其他源程序文件中被访问。归纳起来,auto和register变量属于动态存储类别的变量,而extern和static变量属于静态存储类别的变量。4.2.2嵌入式C语言编程
在主函数中,第1次调用fun()时,初始化静态局部变量n为0,执行n++,输出为1。第2次调用fan()时,静态局部变量n仍然存在,其值为1,执行n++,输出为2。第3次调用fun()时,输出为3。在多次调用fun()时,可以简单地理解仅仅第1次执行“staticintn;语句。在一般情况下,全局变量默认是外部的,即定义全局变量n的如下两种方式是等价的:intn=1:externintn=1;//如果不初始化。就变成声明变量n了如下方式定义的全局变量n为静态全局变量:staticintn=1;静态全局变量的作用域只限于本源程序文件。静态全局变量和普通全局变量的区别是静态全局变量只能初始化一次。以防止在其他源程序文件中被访问。归纳起来,auto和register变量属于动态存储类别的变量,而extern和static变量属于静态存储类别的变量。4.2.2嵌入式C语言编程3)内存组织结构尽管内存空间很大,但程序只能直接访问其中的一部分空间,内存的低地址空间被操作系统占用,其余的高地址空间划分为4个部分,大致结构如图4.6所示,其说明如下。4.2.2嵌入式C语言编程①代码段(codesegment):用来存放程序执行代码(编译后的代码),又称为文本段(textsegment)。这部分区域的大小在程序执行前就已经确定,并且该内存区域属于只读。②数据段(datasegment):用于存放程序执行中使用的那些变量的值,包含以符号开始的块(BlockStartedbySymbol,BSS)段和静态数据区两个部分。BSS段用来存放程序中未初始化的外部变量和未初始化的静态局部变量。在执行程序时,BSS段会预先清空,所以存放在BSS段中的变量均默认初始化为0。这就是为什么外部变量和静态局部变量可以不始化,但是会被赋予默认值0;另外的静态数据区用来存放程序中已初始化的外变量、静态局部变量和常量。③堆空间(heapsegment):堆是用于存放进程(进程简单理解为程序的一次执行)执行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc()等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free()等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。从堆分配的内存仅能通过指针访问。这里的堆与数据结构中队列的概念是不同的。一般来讲在32位系统中堆内存可以达到4GB的空间,从这个角度来看堆内存几乎是没有什么限制的。对于堆空间频繁地malloc/free会造成堆空间的不连续,从而造成大量的碎片,使程序效率降低。4.2.2嵌入式C语言编程一般来讲在32位系统中堆内存可以达到4GB的空间,从这个角度来看堆内存几乎是没有什么限制的。对于堆空间频繁地malloc/free会造成堆空间的不连续,从而造成大量的碎片,使程序效率降低。堆空间是由动态分配函数分配的内存空间,一般速度比较慢,而且容易产生内存片通常堆空间是向高地址方向生长的。程序员可以方便地管理堆空间。④栈空间(stacksegment):又称堆栈,存放程序中临时创建的局部变量(但不包括用static定义的静态变量)、函数参数和函数返回地址。在函数被调用时,其参数也会被压入发起调用的进程中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以機特别方便用来保存/恢复调用现场。从这个意义上讲,可以把栈看成一个存放、交换临时数据的内存区。这里栈的操作方式类似于数据结构中的根。一般地,栈空间有一定的大小,通常远小于堆空间。例如,在VC++6.0中默认的栈空间大小是1MB,程序员可以修改这个值。栈空间由系统自动分配,速度较快,程序员无法控制栈空间。通常栈空间是向低地万向生长的。1)变量静态分配和动态分配方式变量静态分配方式是指在程序编译期间分配固定的存储空间的方式。该存储分配方式通常是在变量定义时就分配存储单元并一直保持不变,直至整个程序结束。例如:inta[10];一旦遇到该定义,系统就采用静态分配方式为数组a分配10个int整数空间。无论程序向数组a中放不放元素,这一片空间都被占用。它也属于自动变量,当超出作用范围时系统自动释放其内存空间。4.2.2嵌入式C语言编程变量静态或者动态分配方式与变量的存储类别是两个不同的概念。程序中的每个变量都有存储类别(没有指出存储类别时均采用默认的存储类别),存储类别确定了该变量存放在内存的什么地方,所有变量都是采用静态分配方式。变量静态或者动态分配方式主要是针对指针变量(或者数组)指向的空间而言的。变量动态分配方式是指在程序执行期间根据需要动态申请堆空间的方式。C语言提供了一套机制可以在程序执行时动态分配存储空间,常见的动态分配函数如表4.3所示,其中size_t为unsignedint类型的别名。4.2.2嵌入式C语言编程
上述代码先定义一个字符指针变量p,然后使用malloc()函数为其分配长度为10个字符的存储空间,将该存储空间的首地址赋给p,再将字符串"China"放到这个存储空间中所以第1个printf语句输出的是首地址的字符,即’C’,而第2个printf语句输出的是整个字符串,即"China"。最后的free(p)语句用于释放p所指向的空间。如果程序员在程序中采用动态分配方式分配大量的内存空同,而用完后不及时释放,可能会很快消耗完应用程序的内存空间,称之为内存泄露。归纳起来,变量静态分配和动态分配的差异如表4.4所示。4.2.2嵌入式C语言编程4.结构体变量的初始化在结构体变量定义的同时可以给各个成员项赋初值,即结构体变量的初始化。结构体变量初始化的一般格式如下:struct结构体类型变量={初始数据表};在对结构体变量赋初值时,C编译程序按每个成员在结构体中的顺序一一对应赋初值,不允许跳过前面的成员给后面的成员赋初值:但可以只给前面的若干个成员赋初值,对于后面未赋初值的成员,对于数值型和字符型数据,系统自动赋初值零。例如,以下语句定义了一个Student结构体类型的变量stud,并对该变量进行初始化:structStudentstud={"李明",'M',20,88,"2010-01"};如果一个结构体变量内又嵌套另一个结构体变量,则对该结构体变量初始化时仍按顺序写出各个初始值。5.结构体变量的内存分配结构体变量的内存空间大小为所有成员空间大小之和,但需要考虑内存对齐问题。1)结构体的内存对齐内存地址对齐是计算机语言自动进行的,也是编译器所做的工作。但这不意味着程序员不需要做任何事情,因为如果能够遵循某些规则,可以让编译器做得更好。处理器一般不是按字节块来存取内存的,而是以双字节、4个字节、8个字节甚至32个字节为单位来存取内存,将这些存取单位称为内存存取粒度。4.2.2嵌入式C语言编程
例如对于双字节存取粒度的处理器,若从地址0读取数据,其次数是单字节存取粒度处理器的一半,而从地址1读取数据时由于地址1没有和内存存取边界对齐,处理器会做一些额外的工作。由于每次内存存取都会产生一个固定的开销,最小化内存存取次数将提升程序的性能。像地址1这样的地址被称为非对齐地址,会减缓存取的速度。VC++(32位系统)中的内存对齐原则如下。基本数据类型的自身对齐值:对于char型数据,其自身对齐值为1个字节,short型为2个字节,对于int、float和double类型,其自身对齐值为4个字节。结构体类型的自身对齐值:其成员中自身对齐值最大的那个值。自定义对齐值:用宏命令#Pragmapack(n)自定义对齐值为n,用宏命令#pragmapack()取消自定义对齐。当指定自定义对齐值时,取自身対齐值和自定义对齐值中较小的那个值。所谓有效对齐值N,是最终用来决定数据存放地址方式的值,即表示”对齐在N上”,也就是说该数据的“存放起始地址%N=0”。结构体变量中的成员都是按定义的先后顺序排放的,第一个成员的起始地址就是该结构体变量的起始地址。结构体变量的成员要对齐排放,结构体变量本身也要根据自身的有效对齐值取整(就是结构体变量中成员占用的总长度需要是结构体有效对齐值的整数倍)。例如定义以下结构体变量x:4.2.2嵌入式C语言编程
结构体变量x中成员a的有效对齐值N1为4,成员b的有效对齐值N2为1,成员c的有效对齐值N3为2,x的有效对齐值N为MAX{4,1,2}=4。现在要分配x的内存空间,假设其起始地址为0x12ff78(其地址值满足模N1余0),先为成员a分配4个字节,其下一字节地址为0x12ff7c,由于满足于0x12ff7c%N2=0,则为成员b分配1个字节,其下一字节地址0x12ff7d,而0x12ff7d%N3≠0,所以不能从该地址开始分配成员c的空间,向后跳过一个字节到地止0x12ff7e,而0x12ff7e%N3=0,从该地址开始分配成员c的空间,这样结构体变量x的内存分配如图4.7所示(图中从左到右表示地址从低到高),其总空间为2N=8(一定是N的整数倍)。4.2.2嵌入式C语言编程
又如定义以下结构体变量y:
结构体变量y中成员a的有效对齐值N1为1,成员b的有效对齐值N2为4,成员c的有效对齐值N3为2,y的有效对齐值N为MAX{1,4,2}=4。现在要分配y的内存空间,假设其起始地址为0(其地址值满足模N1余0),先为成员a分配1个字节,其下一字节地址为1,而1%N2≠0,则不能从此开始为成员b分配空间,后跳过3个字节到地址4,满足4%N2=0,所以从地址4开始为成员b分配4个字节,其下一字节地址为8,满足8%N3=0,则从地址8开始为成员c分配2个字节。由于y的有效对齐值N为4,采用取整规则,即任何结构体变量的大小为其有效对齐值N的整数倍,所以y共分配3N(即12)个字节,它的内存分配如图4.8所示。4.3规范化编程什么叫规范?在C语言中不遵守编译器的规定,编译器在编译时就会报错,这个规定叫作规则。但是有一种规定,它是一种人为的、约定成俗的,即使不按照那种规定也不会出错,这种规定就叫作规范。虽然我们不按照规范也不会出错,但是程序是让人看的,是要分享给队友或者领导,甚至是任何陌生的人共享交流。因此,程序写的规范,不仅看着很整齐、很舒服,有美感,而且把代码写规范则,程序还不容易出错。那么代码如何写才能写得很规范呢?代码的规范化不是一蹴而就,里面细节很多,很多时候需要不停地写代码练习,不停地领悟,慢慢地才能掌握的一种编程习惯。代码规范化有很多内容,这里列举一些程序规划化方法,以提升大家的规范化认识。4.3.1程序排版(1)程序块要采用缩进风格编写,缩进的空格数为4个。说明:对于由开发工具自动生成的代码可以有不一致。(2)相对独立的程序块之间、变量说明之后必须加空行。例如,下面的例子不符合规范。应书写如下:4.3.1程序排版(3)较长的语句(>80字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当缩进,使排版整齐、语句可读。例如:4.3.1程序排版(3)较长的语句(>80字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当缩进,使排版整齐、语句可读。例如:4.3.1程序排版(4)循环、判断等语句中若有较长的表达式或语句,则要进行适应的划分,长表达式要在低优先级操作符处划分新行,操作符放在新行之首。例如:4.3.1程序排版(5)若函数或过程中的参数较长,则要进行适当的划分。例如:(6)不允许把多个短语句写在一行中,即一行只写一条语句。例如,如下例子不符合规范。(7)if、for、do、while、case、switch、default等语句自占一行,且if、for、do、while等语句的执行语句部分无论多少都要加括号{}。例如,如下例子不符合规范。4.3.1程序排版(8)对齐只使用空格键,不使用TAB。说明:以免用不同的编用器阅读程序时,因TAB键所设置的空格数目不同而造成程序布局不整齐,不要使用BC作为编器合版本,因为BC会自动将8个空格变成一个TAB键,因此使用BC合入的版本大多数会将缩进变乱。(9)函数或过程的开始、结构的定义及循环、判断等语句中的代码都要采用缩进风格,case语句下的情况处理语句也要遵从语句缩进要求。(10)程序块的分界符(如C/C++语言的大括号“{”和“}”)应各独占一行并且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、累的定义、结构的定义、枚举的定义,以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。例如,下面的例子不符合规范。4.3.1程序排版应书写如下:(11)在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如“->”),后面不应加空格。说明:采用这种松散方式编写代码的目的是使代码更加清晰。4.3.1程序排版
由于留空格所产生的清晰性是相对的,所以,在已经非常清晰的语句中没有必要再留空格,如果语句已足够清断则括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格。因为在C/C++语言中括号已经是最清断的标志了。在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连留两个以上空格。例如:1、返号、分号只在后面加空格。inta,b,c;2、比较操作符,值操作符“=”、“+=”,算术操作符“+”、“%”,逻辑操作符“&&”、“&”,位域操作符“<<”、“^”等双目操作符的前后加空格。3、“!”、“~”、“++”、“——”、“&”(地址运算符)等单目操作符前后不加空格。4.3.1程序排版4、“->”、“.”前后不加空格5、if、for、while、switch等与后面的括号间应加空格,使if等关键字更为突出、明显。(12)一行程序以小于80字符为宜,不要写得过长。4.3.2代码注释(1)一般情况下,源程序有效注释量必须在20%以上。说明:注释的原则是有助于对程序的阅读理解,在该加的地方都加了,注释不宜太多也不能太少,注释语言必须准确、易懂、简洁。(2)说明性文件(如头文件.h文件、.inc文件、.def文件、编译说明文件.cfg等)头部应进行注释,注释必须列出:版权说明、版头号、生成日期、作者、内容、功能、与其他文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。例如,下面这段头文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。4.3.2代码注释(3)源文件头部应进行注释,必须列出:版权说明、版头号、生成日期、作者、模块目的/功能、主要函数及其功能、修改日志等。例如,下面这段源文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。
说明:Description一项描述本文件的内容、功能、内部各部分之间的关系,以及本文件与其他文件关系等。History是修改历史记录列表,每条修改记录应包括修改日期、修改者和修改内容简述。4.3.2代码注释(4)函数头部应进行注释,必须列出:函数的目的/功能、输入参数、输出参数,返回值、调用关系(函数、表)等。例如,下面这段函数的注释比较准确,当然,并不局限于此格式,但上述信息建议要包含在内。(5)边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。(6)注释的内容要清楚、明了,含义准确,防止注释二义性。说明:错误的注释不但无益反而有害。4.3.2代码注释(7)避免在注释时使用缩写,特别是非常有用的缩写。说明:在使用缩写时或之前,应对缩写进行必要的说明。(8)注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不可放在下面,如放于上方则需与其上面的代码用空行隔开。例如,下面的例子不符合规范。4.3.2代码注释(9)对于有物理含义的变量、常量,如果其命名不是充分自注释的,在声明时都必须加以注释,说明其物理含义。变量、常量、宏的注释应放在其上方相邻位置或右方。例如:(10)数据结构声明(包括数组、结构、类、校举等),如果其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注释放在此域的右方。例如,可按如下形式说明枚举、数据、联合结构。4.3.2代码注释(9)对于有物理含义的变量、常量,如果其命名不是充分自注释的,在声明时都必须加以注释,说明其物理含义。变量、常量、宏的注释应放在其上方相邻位置或右方。例如:(10)数据结构声明(包括数组、结构、类、校举等),如果其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注释放在此域的右方。例如,可按如下形式说明枚举、数据、联合结构。4.3.2代码注释(11)全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意事项等的说明。例如:4.3.2代码注释(12)注释与所描述内容进行同样的缩排。说明:可使程序排版整齐,并方便注释的阅读与理解。例如,下面的例子排列不整齐,阅读稍感不方便。4.3.2代码注释(13)将注释与其上面的代码用空行隔开。例如,下面的例子显得代码过于紧凑。(14)对变量的定义和分支语句(条件分支、循环语句等)必须编写注释。说明:这些语句往往是程序实现某一特定功能的关键,对于维护人员来说,良好的注释帮助更好地理解程序,有时甚至优于看设计文档。4.3.2代码注释(15)对于switch语句下的case语句,如果因为特殊情况需要处理完一个case后进入下一个case处理,必须在该case语句处理完、下一个case语句前加上明确的注释。说明:这样比较清楚程序编写者的意图,有效防止无故遗漏break语句。例如(注意斜体加粗部分):4.3.2代码注释(16)避免在一行代码或表达式的中间插入注释。说明:除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差。(17)通过对函数或过程、变量、结构等正确的命名,以及合理地组织代码的结构,使代码成为自注释的。说明:清晰准确的函数、变量等的命名,可增加代码可读性,并减少不必要的注释。4.3.2代码注释(18)在代码的功能、意图
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 煮茧操作工岗前流程优化考核试卷含答案
- 高空坠落救援应急预案
- 2026年高职(水利水电建筑工程)水工建筑物施工技术测试题及答案
- 中学生职业规划故事集
- 2026五年级道德与法治上册 家庭活动课余参与
- 北京大学2025学生就业服务指南
- 2026年商场油烟管道定期清洗协议
- 勾股定理及其应用第3课时利用勾股定理计算、作图课件2025-2026学年人教版八年级数学下册
- 运动成就健康-走进全面健康生活
- 助力提效赋能竞争-专业商务代办 释放企业潜能
- 宠物医疗化验员技能大赛题库
- 考公二十四节气考试题及答案
- 负荷计算表-冷负荷热负荷
- 2025湖北省高考生物试卷(含解析)
- 2025浙江宁波市水务环境集团有限公司招聘笔试参考题库附带答案
- 窗口人员礼仪培训课件
- 工业厂房施工环境保护体系与措施
- (医疗药品管理)某大型制药集团营销大纲
- 树木砍伐合同简单协议书
- (完整版)材料科学基础笔记
- 高三日语复习3:高考日语语法翻译练习题
评论
0/150
提交评论