MISRA工业标准C语言编程规范
02.
03.
04.
05. MISRA (The Motor Industry Software Reliability Association 汽车工业软件可靠
06.
07.性联会) 是位于英国的一个跨国汽车工业协会,其成员包括了大部分欧美汽车生产商。其核心使命是为汽车工业提供服务和协助,帮助厂方开发安全的、高可靠性的嵌入式软件。这个组织最出名的成果是所谓的MISRA C Coding Standard,这一标准中包括了127条C语言编码标准,通常认为,如果能够完全遵守这些标准,则你的C代码是易读、可靠、可移植和易于维护的。最近很多嵌入式开发者都以MISRA C来衡量自己的编码风格,比如著名的uC/ OS-II就得意地宣称自己99%遵守MISRA标准。而《嵌入式开发杂志》也专门载文号召大家学习。编码规范通常是一个公司自定的“土政策”,居然有人去做标准,而且还得到广泛
08.
09.的认可,这不禁引起我强烈的兴趣。可惜这份标准的文本需要花钱去买,而且短短几十页 ,要价非常昂贵。MISRA在网上公布了一些文档,其中有关于MISRA C Coding Standard的Clarification报告,从中间你可以大致猜到MISRA标准本身是什么。我仔细阅读了这些文档,并且通过阅读其他一些介绍性文档,大致了解了MISRA标准的主要内容。这些条款确有过人之处,对于C/C++语言工程项目的代码质量管理能够起到良好的指导性作用,对于大部分软件开发企业来说,在MISRA的基础上适当修改就可以形成自己的规范。当然其中也有一些过于严苛的东西,这就需要各个开发部门灵活处理了。我个人的体会,编码规范虽然很简单,但是要完全执行,不折不扣,需要开发部门有很高的组织性和纪律性,并且有很好的代码评审机制。因此,如果能够严格地遵守编码规范,本身就是一个开发部门实力的证明。这里不可能将所有规则一一列出(事实上正式文本我一条也没看到),只列出一些比较有意思的条款,让大家有机会了解MISRA的风格。具体的内容,感兴趣的朋友可以自己到www .misra.org.uk去了解。
10.
11.Rule 1.严格遵循ANSI C89标准,不允许任何扩展。
12.
13.Rule 3. 如果要嵌入汇编语言,则必须将所有汇编语句包装在C函数里,而且这些函数中只有汇编语句,没有常规C语句。
14.
15.
16.
17.Rule 7. 不得使用三元操作符(? : )
18.
19.Rule 10. 不得残留被注释掉的废代码。
20.
21.Rule 11. 所有标识符不超过31字符。
22.
23.Rule 12. 不同名空间中的变量名不得相同。
24.
25. 例如:
26.
27. typedef
struct
MyStruct {... } MyStruct; (违规)
28.
29.
struct
Person {
30.
31.
char
* name;
32.
33. ...
34.
35. };
36.
37.
char
name[32]; (违规)
38.
39.Rule 13. 不得使用
char
,
int
,
float
,
double
,
long
等基本类型,应该用自己定义的类型显示表示类型的大小,如CHAR8, UCHAR8, INT16, INT32, FLOAT32, LONG64, ULONG64等。
40.
41.Rule 14. 不得使用类型
char
,必须显示声明为unsigned
char
或者signed
char
。
42.
43.Rule 18. 所有数字常数应当加上合适的后缀表示类型,例如51L, 42U, 34.12F等。
44.
45.Rule 19. 禁止使用八进制数。(因为086U这样的常数很容易引起误解)。
46.
47.Rule 21. 不得定义与外部作用域中某个标识符同名的对象,以避免遮盖外部作用域中的标识符。
48.
49.Rule 23. 具有文件作用域的对象尽量声名为
static
的。
50.
51.Rule 24. 在同一个编译单元中,同一个标识符不应该同事具有内部链接和外部链接的声名。
52.
53. 这里我略作说明:
54.
55.
56.
57. 我们通常将一些放在头文件里的变量声名为“外部链接”的,如:
58.
59.
extern
UINT32 g_count;
60.
61. 对于“使用”这个变量的.c文件来说,这很好,因为g_count始终保持外部链接性质。可是对于定义g_count(实际分配空间)的.c文件来说,如果包含了上述的头文件,则在这个编译单元里就发生了内部链接和外部链接的冲突。解决办法是,定义g_count的文件尽量不要包含声名g_count的头文件。个人感觉这不是任何时候都做得到的,尤其是在对付遗留代码的时候。
62.
63.Rule 25. 具有外部链接性质的标识符应该只声明一次。
64.
65.Rule 27. 外部对象不得在多个文件中声名。
66.
67.Rule 28. 禁止使用register关键字。
68.
69.Rule 29. 自动对象(栈对象)使用前必须赋初值。
70.
71.Rule 33. 操作符&&和||的右侧表达式不得具有副作用(side-effect)。
72.
73. 也就是说,象
if
(x == 20 && ++y == 19)这样的表达式被禁止。
74.
75.Rule 35. 在返回布尔值的表达式中不得出现赋值操作。
76.
77. 也就是说,我们常用的
if
(!(fp = fopen(
"fname"
,
"r"
))) {
}
78.
79. 被禁止。
80.
81.Rule 37. 不得对有符号数施加位操作,例如 1 << 4 将被禁止,必须写 1UL << 4;
82.
83.Rule 39. 不得对有符号表达式施加一元
"-"
操作符。
84.
85.Rule 40. 不得对有副作用的表达式施加
sizeof
操作符。
86.
87.Rule 42. 除了循环控制语句,不得使用逗号表达式。
88.
89.Rule 44. 禁止冗余的显式转型。比如:
double
pi = (
double
) 3.1416F;
90.
91.Rule 45. 禁止从任意类型到指针的强制转型,禁止从指针到任意类型的强制转型。
92.
93. 例如:
void
* p = (
void
*)0xFFFF8888UL;
94.
95.Rule 49. 显示测试值是否为零。
96.
97.Rule 50. 不得显式判断浮点数的相等性和不等性。
98.
99.Rule 52. 不得遗留“永远不会用到”的代码。
100.
101.Rule 53. 所有非空语句必须具有副作用。
102.
103.Rule 55. 除了
switch
语句,不得使用标号(label)。
104.
105.Rule 56. 不得使用
goto
.
106.
107.Rule 57. 不得使用
continue
。
108.
109.Rule 58. 除了
switch
语句,不得使用
break
.
110.
111.Rule 59.
if
,
else
if
,
else
,
while
,
do
..
while
,
for
语句块必须使用{}括起。
112.
113.Rule 60. 任何
if
..
else
if
语句,最后必须有一个收尾的
else
。例如:
114.
115.
if
(ans ==
'Y'
) {
116.
117. ...
118.
119. }
120.
121.
else
if
(ans ==
'N'
) {
122.
123. ...
124.
125. }
126.
127.
else
if
(ans ==
'C'
) {
128.
129. ...
130.
131. }
132.
133.
else
{
134.
135. ;
136.
137. }
138.
139.Rule 67. 循环计数器的值不得在循环体内修改。
140.
141.Rule 70. 禁止任何直接和间接的递归函数调用。
142.
143.Rule 82. 每个函数只能有一个推出点。
144.
145.Rule 86. 如果一个函数可能返回错误信息,则调用后必须加以测试。
146.
147.Rule 92. 不应该使用#undef
148.
149.Rule 95. 不得将宏作为参数传给宏函数
150.
151.Rule 98. 在一个宏定义中,#或##符号只能出现一次。
152.
153.Rule 101. 禁止指针运算(代之以数组下标运算)。
154.
155.Rule 102. 禁止超过两级的指针。
156.
157.Rule 104. 禁止使用指向函数的非常量指针。
158.
159.Rule 106. 不得将栈对象的地址传给外部作用域的对象。
160.
161.********************************************************************
162.
163.后面的规则针对实时嵌入式系统,对其他类型的开发未必适用,如:
164.
165.Rule 118. 禁止使用动态堆分配(也就是不得使用malloc, calloc和realloc)。
166.
167.Rule 119. 禁止使用errno。
168.
169.Rule 120. 禁止使用offsetof.
170.
171.Rule 121. 禁止使用<locale.h>
172.
173.Rule 122. 禁止使用setjmp, longjmp.
174.
175.Rule 123. 禁止使用<signal.h>
176.
177.Rule 124. 禁止使用<stdio.h>(不能用printf, scanf了!)
178.
179.Rule 125. 禁止使用atoi, atof, atol。(这个我很赞成,建议使用strtol, strtod等函数)
180.
181.Rule 126. 禁止使用abort, exit, getenv。
182.
183.Rule 127. 禁止使用<time.h>
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>