Advertisement

MISRA C Coding Standard

阅读量:

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"))) { /* error */ }
被禁止。

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>

全部评论 (0)

还没有任何评论哟~