单片机编程规范----主编:郑思鹏杨宗禄梁荣生夏立闯赵龙C语言编程规范参考资料:林锐高质量C++/C编程指南1.02001-7-24AndrewKoenigCTrapsandPitfalls(C陷阱与缺陷)BYD-LPJ-A-10007软件编程规范A02010-7-24MISRAC2004i++案例分析以下代码运行结果:inti=3,y;y=i*i++;y=?A9B12C16DA或CE不知道类似的,i*++i;i++*i;++i*i;i++*i++;i++*i++*i++;结果是多少?if(ishigh&&(x==i++))执行这条语句后i是否自动加1?问题:如何保证程序员的理解是正确的?如何保证编译器的理解是正确的?如何保证编译器的理解与程序员是一致的?如何保证所有人的理解是一致的?如何保证编译器不同版本、不同场合都是一致的?switch案例byteExSw(bytechr){byteret;switch(chr&0xC0){case0x00:{ret=0;break;}case0x40:{ret=1;break;}case0x80:{ret=2;break;}case0xC0:{ret=3;break;}}returnret;}用CWHC08V5.1编译运行示例代码,结果是什么?用V6.2编译运行,结果是什么?交替编译运行,结果是什么?增加default语句,运行结果如何?对ret进行初始化,运行结果如何?将0xC0改为0xC0u,运行结果如何?short案例以下代码用CodewarriorHC08V5.1编译,功能正常;但用V6.2编译,功能与预期不符。start=OSTime;/*start、end、OSTime数据类型为INT16U*/end=start+1300u;cnt=3;/*连续3次检测信号*/for(;(((OSTime-end)&0x8000U)!=0)&&(cnt!=0);cnt--){if(GetSnrSta()!=HALL_ON){cnt=4;}}经反复调试,发现数据类型INT16U对编译结果有影响。INT16U为2字节无符号数(unsignedshortint),将类型定义中short去掉后,V6.2编译结果正确。启示:1.有变化即有差异,即有风险。开发工具如此,软件代码也是这样。哪怕是再小的变化,看起来绝对没有影响,实际上可能存在想象不到的错误。2.开发工具存在bug,须进行白盒测试,确保程序实际运行结果与预期一致。=与==if(x=y)if(x=5)if(5==x)x==y;类似的,&与&&,|与||,与,与/*案例y=x/*p;y=x/*p;y=x/(*p);数据类型问题1:执行以下程序,result_8的值是多少?uint8_tport=0x5a;uint8_tresult_8;result_8=(~port)4;问题2:执行以下程序,d的值是多少?uint16_ta=10;uint16_tb=65531;uint32_tc=0;uint32_td;d=a+b+c;危险的类型转换u32x=u16a+u16b;u32a=(uint32_t)(u16a*u16b);f64a=u16a/u16b;f32a=(float32_t)(u16a/u16b);f64a=f32a+f32b;f64a=(float64_t)(f32a+f32b);整数溢出if((x+y)z)if((x-y)0)假定a和b是非负整型变量,我们需要检查a+b是否溢出,一种想当然的方式是:if((a+b)0)然而,正确的方式是:if(((unsigned)a+(unsigned)b)INT_MAX)assert案例定义assert宏:#defineassert(e)if(!(e))assert_error(__FILE__,__LINE__)#defineassert(e){if(!(e))assert_error(__FILE__,__LINE__);}#defineassert(e)((void)((e)||assert_error(__FILE__,__LINE__)))else案例大小写转换历史#definetolower(c)((c)-'A'+'a')#definetoupper(c)((c)-'a'+'A')#definetolower(c)(isupper(c)?((c)-'A'+'a'):(c))#definetoupper(c)(islower(c)?((c)-'a'+'A'):(c))inttolower(intch){if(ch='A'&&ch='Z')ch=(ch-'A')+'a';returnch;}inttoupper(intch){if(ch='a'&&ch='z')ch=(ch-'a')+'A';returnch;}toupper(*p++)C语言不安全性C语言的不安全性在于:1程序员产生错误2程序员不了解语言3编译器的行为同程序员预期的不同4编译器包含错误5运行时错误程序员产生错误程序员产生的错误,简单的可以是变量名字的书写错误,或者更为复杂的错误,如对算法的误解。首先,关于语言的风格和表达,使用C可以编写出良好布局的、结构化的和表达性强的代码。还可以使用它编写出不正当的和特别难以理解的代码。很明显,后者对于安全相关的系统是不可接受的。其次,C的语法特性足以使得书写错误也能产生完全有效的代码。例如,在“==”(逻辑比较)的地方写成“=”(赋值)是很常见的,而且最终结果也几乎总是有效的(但它是错误的);而if语句的结尾出现的多余分号能完全改变代码逻辑。第三,C的基本观点是假设程序员知道他们在做什么,这意味着错误即使出现也不会被语言注意到而通过。例如:变量名书写错误,恰好与另外一个变量同名,编译器无法发现此错误。赋值语句x=y误写为x==y,或x==y误写为x=y(|与||,&与&&,&=与=等等),这些书写错误不会被认为C语言错误。循环语句for(i=0;i10;i++);漏写(或多写)了一个分号。程序员不了解语言C语言中有相当多的地方能使程序员轻易产生误解。例如运算符优先级的规则。这些规则是良好定义的,但也非常复杂,也很容易对某特定表达式中运算符的优先级做出错误的假设。例如:对运算顺序、运算符优先级误解,或记错,如chr&0x042,程序员的意图与实际存在差异或程序员漏写了括号。问题:谁能正确理解和记忆运算优先级,保证不犯错?如何保证其他人(阅读者、维护者)跟你一样?其他人能否正确理解你的意图并判断是否存在书写错误?编译器的行为同程序员预期的不同C语言中许多地方是未经完善定义的,因此其行为可能会随着编译器的改变而改变。某些情况下,其行为甚至在同一个编译器内也会根据上下文而发生变化。例如:对联合体、结构体理解与实际编译器不一致。对数据类型理解错误,如sizeof(int)可能为2或4。编译器包含错误编译器(及其链接器等)本身就是软件工具。编译器可能不会始终正确地编译代码。例如,在特定环境下它们可能同语言标准相违背,或者其本身可能就包含“bug”。因为C语言中存在许多难以理解的地方,编译器的编写者很容易错误地解释和实现标准。而且编译器的编写者有时会有意改变标准。许多编译器存在软件bug,其版本声明中对此有详细说明。在产品开发过程中,遇到过以下问题:1编译器(RaisonanceRIDEMRKII)出现过2次错误。2某软件CWHC08V5.1编译正确,V6.2编译结果错误。3某软件CWHC12V3.1编译正确,V4.6编译结果错误。4编译器版本变化导致编译结果错误5编译器使用不当导致编译结果错误运行时错误C的运行时检查能力比较弱。这也是C代码短小有效的原因之一,但是在运行中检查错误就要花费一定的代价。C编译器通常不为某些常见问题提供运行时检查,诸如数学异常(如零除)、溢出、指针地址的有效性,或数组越界错误。intaverage(inta[],intsize){longsum=0;/*防溢出?*/if(a==NULL)return0;if(size==0)return0;for(i=0;isize;i++){sum=sum+a[i];}returnsum/siz;}intx,y,z;…if((x+y)=z)…总则规则1.所有的代码应该遵守ISO9899:1990“ProgrammingLanguageC”。规则2.只有具备统一接口目标代码的时候才可以采用多种编译器和语言。规则3.检查编译器/链接器以确保支持31个有效字符,支持大小写敏感。排版规则6.2.1.程序块采用缩进风格编写,缩进的空格数为4个。规则6.2.5.较长的语句(80字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐、语句可读性强。规则6.2.6.循环、判断等语句若较长,则要进行适当的划分。规则6.2.7.若函数或过程中的参数较长,则要进行适当的划分。规则6.2.11.程序块的分界符应各独占一行并且位于同一列,同时与引用它们的语句左对齐,‘{}’之内的代码块在‘{’右边数格处左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。•理由提高软件的可读性,减少编码时带来错误的可能性排版规则6.2.4.相对独立的程序块之间、变量说明之后必须加空行。规则6.2.8.不允许把多个短语句写在一行中,即一行只写一条语句。规则6.2.9.switch、while、do...while和for语句的主体必须是复合语句•理由可读性,防止”{““}”缺失导致的书写错误•非法排版bytei,j,sum;i=0;j=10;for(;ij;i++);sum+=i;if(sum==45)puts(“OK”);•合法排版bytei;/*循环变量*/bytej;/*循环次数*/wordsum;/*累加和*/sum=0;j=10;for(i=0;ij;i++){sum=sum+i;}if(sum==45){puts(“OK”);}else{}TAB键规则6.2.10.对齐使用空格键,不建议使用TAB键;若用TAB键需将编译器TAB键统一设置为4个空格。•理由使用TAB键容易导致用不同编辑器阅读时布局混乱。空格规则6.2.12.在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符,其后不应加空格。•理由使代码更加清晰。在长语句中,若空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。•例如1逗号、分号只在后面加空格inta,b,c;2双目操作符前后加空格a=b+c;a*=2;a=b1;3单目操作符(!~++–&)不加空格p=&var;*p=~a;4“-”、“.”前后不加空格pCanObj-id=obj.id;5if、for、while、switch与后面的括号间应加空格if((a=b)&&(cd))6*和&紧靠变量名int*p=&var;注释规则6.3.1.一般情况下,源程序有效注释量必须在20%以上规则6.3.2.说明性文件头部应进行注释规则6.3.3.源文件头部应进行注释规则6.3.4.函数头部应进行注释规则6.3.5.注释的内容要清楚、明了,含义准确,防止注释二义性规则6.3.6.避免在注释中使用缩写,特别是非常用缩写注释方式规则6.3.18.注释格式统一使用“/*……*/”;字符序列/*不应出现在注释中规则6.3.19.注释应考虑程序易读及外观排版的因素,建议多使用中文注释位置规则6.3.7.注释应与其描述的代码相近规则6.3.11.注释与所描述内容进行同样的缩排规则6.3.15.不允许在一行代码或表达式的中间插入注释注释对象规则6.3.13.对变量的定义和分支语句(条件分支、循环语句等)必须编写注释规则6.