原创 Misra C 编程标准

2011-6-21 10:34 2674 9 9 分类: 汽车电子

Misra C 编程标准

 MISRA (The Motor Industry Software Reliability Association 汽车工业软件可靠性联会) 是位于英国的一个跨国汽车工业协会,其成员包括了大部分欧美汽车生产商。其核心使命是为汽车工业提供服务和协助,帮助厂方开发安全的、高可靠性的嵌入式软件。这个组织最出名的成果是所谓的MISRA C Coding Standard,这一标准中包括了127条C语言编码标准,通常认为,如果能够完全遵守这些标准,则你的C代码是易读、可靠、可移植和易于维护的。最近很多嵌入式开发者都以MISRA C来衡量自己的编码风格,比如著名的uC/OS-II就得意地宣称自己99%遵守MISRA标准。而《嵌入式开发杂志》也专门载文号召大家学习。编码规范通常是一个公司自定的“土政策”,居然有人去做标准,而且还得到广泛的认可,这不禁引起我强烈的兴趣。可惜这份标准的文本需要花钱去买,而且短短几十页,要价非常昂贵。MISRA在网上公布了一些文档,其中有关于MISRA C Coding Standard的Clarification报告,从中间你可以大致猜到MISRA标准本身是什么。我仔细阅读了这些文档,并且通过阅读其他一些介绍性文档,大致了解了MISRA标准的主要内容。这些条款确有过人之处,对于C/C++语言工程项目的代码质量管理能够起到良好的指导性作用,对于大部分软件开发企业来说,在MISRA的基础上适当修改就可以形成自己的规范。当然其中也有一些过于严苛的东西,这就需要各个开发部门灵活处理了。我个人的体会,编码规范虽然很简单,但是要完全执行,不折不扣,需要开发部门有很高的组织性和纪律性,并且有很好的代码评审机制。因此,如果能够严格地遵守编码规范,本身就是一个开发部门实力的证明。

    这里不可能将所有规则一一列出(事实上正式文本我一条也没看到),只列出一些比较有意思的条款,让大家有机会了解MISRA的风格。具体的内容,感兴趣的朋友可以自己到www.misra.org.uk去了解。

Rule 1. 严格遵循ANSI C89标准,不允许任何扩展。

Rule 3. 如果要嵌入汇编语言,则必须将所有汇编语句包装在C函数里,而且这些函数中只有汇编语句,没有常规C语句。

 

Rule 7. 不得使用三元操作符(? : )

Rule 10. 不得残留被注释掉的废代码。

Rule 11. 所有标识符不超过31字符。

Rule 12. 不同名空间中的变量名不得相同。

      例如:

        typedef struct MyStruct {... } MyStruct; (违规)

        struct Person {

          char* name;

          ...

        };

        char name[32]; (违规)

Rule 13. 不得使用char, int, float, double, long等基本类型,应该用自己定义的类型显示表示类型的大小,如CHAR8, UCHAR8, INT16, INT32, FLOAT32, LONG64, ULONG64等。

Rule 14. 不得使用类型char,必须显示声明为unsigned char或者signed char。

Rule 18. 所有数字常数应当加上合适的后缀表示类型,例如51L, 42U, 34.12F等。

Rule 19. 禁止使用八进制数。(因为086U这样的常数很容易引起误解)。

Rule 21. 不得定义与外部作用域中某个标识符同名的对象,以避免遮盖外部作用域中的标识符。

Rule 23. 具有文件作用域的对象尽量声名为static的。

Rule 24. 在同一个编译单元中,同一个标识符不应该同事具有内部链接和外部链接的声名。

      这里我略作说明:

  

      我们通常将一些放在头文件里的变量声名为“外部链接”的,如:

      extern UINT32 g_count; // 俗话叫变量声明(对应于变量定义,不分配实际空间)

      对于“使用”这个变量的.c文件来说,这很好,因为g_count始终保持外部链接性质。可是对于定义g_count(实际分配空间)的.c文件来说,如果包含了上述的头文件,则在这个编译单元里就发生了内部链接和外部链接的冲突。解决办法是,定义g_count的文件尽量不要包含声名g_count的头文件。个人感觉这不是任何时候都做得到的,尤其是在对付遗留代码的时候。

Rule 25. 具有外部链接性质的标识符应该只声明一次。

Rule 27. 外部对象不得在多个文件中声名。

Rule 28. 禁止使用register关键字。

Rule 29. 自动对象(栈对象)使用前必须赋初值。

Rule 33. 操作符&&和||的右侧表达式不得具有副作用(side-effect)。

      也就是说,象 if (x == 20 && ++y == 19)这样的表达式被禁止。

Rule 35. 在返回布尔值的表达式中不得出现赋值操作。

      也就是说,我们常用的 if (!(fp = fopen("fname", "r"))) { }

      被禁止。

Rule 37. 不得对有符号数施加位操作,例如 1 << 4 将被禁止,必须写 1UL << 4;

Rule 39. 不得对有符号表达式施加一元 "-" 操作符。

Rule 40. 不得对有副作用的表达式施加sizeof操作符。

Rule 42. 除了循环控制语句,不得使用逗号表达式。

Rule 44. 禁止冗余的显式转型。比如: double pi = (double) 3.1416F;

Rule 45. 禁止从任意类型到指针的强制转型,禁止从指针到任意类型的强制转型。

      例如:void* p = (void*)0xFFFF8888UL;

Rule 49. 显示测试值是否为零。

Rule 50. 不得显式判断浮点数的相等性和不等性。

Rule 52. 不得遗留“永远不会用到”的代码。

Rule 53. 所有非空语句必须具有副作用。

Rule 55. 除了switch语句,不得使用标号(label)。

Rule 56. 不得使用goto.

Rule 57. 不得使用continue。

Rule 58. 除了switch语句,不得使用break.

Rule 59. if, else if, else, while, do..while, for语句块必须使用{}括起。

Rule 60. 任何if..else if 语句,最后必须有一个收尾的else。例如:

      if (ans == 'Y') {

      ...

      }

      else if (ans == 'N') {

      ...

      }

      else if (ans == 'C') {

      ...

      }

      else {

      ;

      }

Rule 67. 循环计数器的值不得在循环体内修改。

Rule 70. 禁止任何直接和间接的递归函数调用。

Rule 82. 每个函数只能有一个推出点。

Rule 86. 如果一个函数可能返回错误信息,则调用后必须加以测试。

Rule 92. 不应该使用#undef

Rule 95. 不得将宏作为参数传给宏函数

Rule 98. 在一个宏定义中,#或##符号只能出现一次。

Rule 101. 禁止指针运算(代之以数组下标运算)。

Rule 102. 禁止超过两级的指针。

Rule 104. 禁止使用指向函数的非常量指针。

Rule 106. 不得将栈对象的地址传给外部作用域的对象。

********************************************************************

后面的规则针对实时嵌入式系统,对其他类型的开发未必适用,如:

Rule 118. 禁止使用动态堆分配(也就是不得使用malloc, calloc和realloc)。

Rule 119. 禁止使用errno。

Rule 120. 禁止使用offsetof.

Rule 121. 禁止使用<locale.h>

Rule 122. 禁止使用setjmp, longjmp.

Rule 123. 禁止使用<signal.h>

Rule 124. 禁止使用<stdio.h>(不能用printf, scanf了!)

Rule 125. 禁止使用atoi, atof, atol。(这个我很赞成,建议使用strtol, strtod等函数)

Rule 126. 禁止使用abort, exit, getenv。

Rule 127. 禁止使用<time.h>

 

 

MISRA是汽车工业C语言编程指导,是嵌入式C语言的编程指导,目前在各个行业,包括:航空,航天,船舶,电信,医疗等行业广泛采纳,是最优秀的嵌入式C语言标准,在MISRA1998版的基础上,最新发布的MISRA2004有一下改进:

? 121条强制遵守,20条建议遵守

? 复杂规则细化

? 模糊规则明确

? 引入一些数学操作的规则

规则分类包括:

? 环境

? 语言扩展

? 文档化

? 字符集

? 标识符

? 类型

? 约束

? 声明和定义

? 初始化

? 数学类型转换

? 指针类型转换

? 表达式

? 控制语句表达式

? 控制流

? Swith语句

? 函数

? 指针和数组

? 结构和联合

? 预处理指令

? 标准库

? 运行时失效

下面概要的介绍MISRA2004中的一些规则:

 

<环境>

Rule1.1(强制):所有的代码应该遵守ISO 9899:1990“Programming Language C”

Rule1.2(强制):只有当具备统一接口的目标代码的时候才可以采用多种编译器和语言

Rule1.4(强制) 检查编译器/连接器以确保支持31一个有效字符,支持大小写敏感

 

<语言扩展>

Rule 2.1(强制):汇编语言应该封装起来并且隔离:

例如:#define NOP asm(“  NOP”)

 

Rule 2.2(强制) :源代码只能采用风格的注释

Rule2.3(强制): 字符序列

 

<初始化>

Rule 9.1(强制):所有变量在使用之前都应该赋值

 

<数学类型转换(隐式)>

Rule 10.1(强制):整型表达式不要隐式转换为其他类型:

    a)转换到更大的整型

    b)表达式太复杂

    c)表达式不是常数是一个函数

    d)表达式不是一个常数是一个返回表达式

 

Rule 10.2(强制):浮点数表达式不要隐式转换为其他类型:

    a)转换到更大的浮点数

    b)表达式太复杂

    c)表达式是一个函数

    d)表达式是一个返回表达式

 

<数学类型转换(明确)>

Rule 10.3(强制):整型表达式的值只能转换到更窄小且是同样符号类型的表达式

Rule 10.4(强制):浮点表达式的值只能转换到更窄小的浮点表达式

 

<数学类型转换>

Rule 10.6(强制):所有的 unsigned类型都应该有后缀”U“

Rule 11.1(强制):指针不能转换为函数或者整型以外的其他类型

 

<表达式>

Rule12.2(强制):表达式的值应和标准允许的评估顺序一致

 

例:

X=b + i++;

不同的编译器给出的结果不一样,b是否先执行?

应:x=b;

       i++;

比如:

X=func(i++,i);

Rule12.3(强制):sizeof操作符不能用在包含边界作用(side effect)的表达式上

 

例:

Int32_t=i;

Int32_t=j;

j=sizeof(i=1234);

表达式并没有执行,只是得到表达式类型int的size

 

Rule 12.4(强制):逻辑操作符&&或者||右边不能包含边界作用(side effect)

 

例:

If(ishight) && (x== i++)),如果ishight=0那么i++不会评估

 

Rule 12.3(建议):++和- -不能和其他表达式用在一个表达式中

例:

U8a=++u8b + u8c--;

 

<控制语句表达式>

Rule13.1(强制):赋值语句不能用在一个产生布尔值的表达式中

例:

If((x=y)!=0)…

更差:

If (x=y)…

 

Rule13.3(强制):浮点表达式不应该测试其是否相等或者不相等

Rule13.4(强制):for控制表达式中不要包含任何浮点类型

Rule13.6(强制):数字变量作为for循环的循环计数不要在循环体内部被修改

例:

Flag=1;

For(i=0;(i<5)&&(flag==1);i++)

{

Flag=0;

i=i+3;

}

 

<控制流>

Rule 14.1(强制):不要有执行不到的代码

例:

Swich(event)

{

Case www;

          do_wakeup();

          break;

          do_more();

}

 

Rule 14.4(强制):goto语句不能使用

Rule 14.5(强制):continue不能使用

Rule 14.6(强制):函数应在函数结束有一个出口

Rule 14.7(强制):witch,while,do ...while,for语句体应是一个混合语句(括号)

Rule 14.10(强制):所有if…else if结构都应该由else结束

 

<Switch语句>

Rule 15.3(强制):switch的最后应是default

Rule 15.4(强制):switch表达式不能使用布尔表达式

例:

Switch(x==0)

{

… …

}

Rule 15.5(强制):每一个Switch语句都应该有一个case

例:

Switch(x)

{

Uint8_t var;

Case 0:

A=b;

}

 

<函数>

Rule16.2(强制):函数不能直接或者间接的调用自己

注:safe-related 系统不能用递归,超出堆栈空间很危险

 

Rule16.8(强制):non-void类型函数的所有出口路径都应该有一个明确的return语句表达式

 

<指针和数组>

Rule17.1(强制):指针的数学运算只能用在指向数组的地址上

Rule17.3(强制):>,>=,<,<=不能用在指针类型除非指向同一个数组

Rule 17.5(建议):不要用2级以上指针

 

<结构和联合>

Rule18.4(强制)不要用Union

 

<预处理指令>

Rule19.1(建议):#include语句的前面只能有其他预处理指令和注释

Rule19.2(建议):#include指令中的头文件名称不能包含非标准的字符

Rule19.5(强制):宏不能在函数体内定义

Rule19.8(强制):类函数宏调用时不能没有它的参数

 

<标准库>

Rule20.1(强制):标准库中的保留标识符,宏和函数不能定义,重定义,和undefined

Rule20.4(强制):动态内存分配不能使用

注:不能使用:malloc,calloc,free,realloc

Rule20.9(强制):输入输出库(stdio.h)不能用在产生嵌入式系统中

Rule20.12(强制):时间处理函数<time.h>不能使用

 

<运行时故障>

Rule 21.1(强制):通过使用一下手段确保把运行时故障最小化:

– 静态分析工具/技术

– 动态分析工具/技术

– 编写明确的代码避免运行时错误

 

QAC Compliance Module for MISRA-C:2004 已经正式发布,成为最快全面支持MISRA2004的工具之一。

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
9
关闭 站长推荐上一条 /1 下一条