MISRA C
一、简介
MISRA C是由汽车产业软件可靠性协会(MISRA)提出的C语言开发标准。其目的是在增进嵌入式系统的安全性及可移植性。针对C++语言也有对应的标准MISRA C++。本文主要关注C语言部分。MISRA C一开始主要是针对汽车产业,不过其他产业也逐渐开始使用MISRA C:包括航天、电信、国防、医疗设备、铁路等领域中都已有厂商使用MISRA C。
MISRA C的第一版《Guidelines for the use of the C language in vehicle based software》是在1998年发行,一般称为MISRA-C:1998.。MISRA-C:1998有127项规则,规则从1号编号到127号,其中有93项是必需要求,其余的34项是推荐使用的规则。
在2004年时发行了第二版的MISRA C的第一版《Guidelines for the use of the C language in critical systems》(或称作MISRA-C:2004),其中有许多重要建议事项的变更,其规则也重新编号。MISRA-C:2004有141项规则,其中121项是必需要求,其余的20项是推荐使用的规则。规则分为21类,从“开发环境”到“运行期错误”。
2012年发布第三版,为当前最新有效的C语言规范版本,称为MISRAC:2012。
Misra C不能100%保证程序不出问题,但是能尽可能的预防,总结一下,基本上使用Misra C具有以下五个维度的优势:
- 提升可靠性
- 提升可读性
- 提升可移植性
- 提升可维护性
- 提升安全性
二、具体规则(翻译)
2.1 一个标准C环境
- 必需。程序应不包含违反标准C语法和限制的内容,也不应超出执行的翻译限制。
- 建议。不应使用语言扩展。
- 必需。不应该出现未定义或是临界的未指定行为。
2.2 不使用的代码
- 必需。不应包含未执行的代码。
- 必需。不应该有死代码。
- 建议。不应包含被声明但未使用的类型。
- 建议。不应包含未使用的标签声明。
- 建议。不应包含未使用的宏定义。
- 建议。函数中不应包含未使用的标记。
- 建议。不应有未使用的函数参数。
2.3 注释
- 必需。不允许嵌套注释。
- 必需。不允许在//注释中进行行拼接。
2.4 字符集
- 必需。八进制和十六进制转义字符序列应被终止。
- 建议。三字母词不应使用。
2.5 标识符
- 必需。外部标识符应区分开。
- 必需。标识符声明在同一作用域且位域应被区分。
- 必需。声明于内部作用域的标识符不应隐藏外部作用域的标识符。
- 必需。宏标识符应被区分。
- 必需。标识符要区别于宏名。
- 必需。Typedef名应是独一无二的标识符。
- 必需。标签应是独一无二的标识符。
- 必需。定义有外部链接的对象或函数的标识符应是独一无二的。
- 建议。定义有内部链接的对象或函数的标识符应是独一无二的。
2.6 类型
-
必需。位域只能被定义为一个合适的类型。
C90:无符号整型和有符号整型。C99:除上述两个以外还有布尔类型。
-
必需。单比特命名的位域不应是有符号类型。
2.7 常量
- 必需。不要使用八进制常量和八进制的转义字符串;
- 必需。所有以无符号类型表示的整型常量都应加上u或U后缀。
- 必需。小写字母l不应用于文字后缀。
- 必需。除非对象类型是指向字符常量的指针,否则字符串不应赋给对象。
2.8声明与定义
- 必需。类型应被显式声明。
- 必需。函数应以原型形式命名参数。
- 必需。所有对象和函数的声明需要使用完全相同的名字和参数。
- 必需。当定义有外部链接的对象或函数时,兼容声明是可见的。
- 必需。外部变量或函数应被在仅一个文件内被声明过。
- 必需。有外部链接的标识符应有一个确切的外部定义。
- 建议。若函数和对象仅被一个单元引用,最好不定义外部链接。
- 必需。静态存储类说明符应在所有具有内部链接的对象和函数的声明中使用。
- 建议。如果一个对象的标识符仅在一个函数内出现,该对象应被定义在一个块域中。
- 必需。内联函数定义时应有静态存储类。
- 建议。当有外部链接的数组被定义,它的大小应明确表示。
- 必需。枚举列表内的枚举值应独一无二。
- 建议。可能的话指针最好指向一个const类型的变量。
- 必需。restrict限定词不应使用。
2.9 初始化
1、强制。自动变量在使用前应该被赋值。
2、必需。结构和和联合体的初始化应在大括号内完成。
3、必需。数组不应被部分初始化。
4、必需。一个对象的元素不应被多次初始化。
5、必需。若一个数组对象被指定初始化,那么数组大小应明确表示。
2.10 基本数据类型
- 必需。操作数不应是不合适的数据类型。
- 必需。表达式字符之间不应使用加减法。
- 必需。不应将表达式的值赋给不同类型或更窄的类型。
- 必需。一个运算符的两个操作数应是相同类型。
- 建议。表达式的值不应被转换成一个不合适的类型。
- 必需。复合表达式的值不应赋给一个有更宽类型的对象。
- 必需。若复合表达式参与数学运算,那么另一个操作数不应是更宽的基本类型。
- 必需。复合表达式的值不应被转换为不同的基本类型分类或更宽的基本类型。
2.11指针类型转换
- 必需。不应在函数指针和其他类型间进行转换。例外:空指针可被转换为函数指针。函数指针可被转换为void类型。函数可以被隐式转换为该函数类型的指针。
- 必需。指向不完全类型的指针与其他类型间的转换不应出现。
- 必需。不同类型指针间不应转换。
- 建议。不应将指针转换为整形变量。
- 建议。不应直接将void类型指针转换为指向对象的指针。
- 必需。算数类型和void类型指针不应转换。
- 必需。指向对象的指针和一个非整型算数类型不应转换。
- 必需。指针指向的const和volatile修饰的变量不应被删除。
2.12 表达式
- 建议。表达式中的运算符优先级应明确。
- 必需。移位运算符的右手操作数应该小于左手操作数的基本类型的位宽。
- 建议。不应使用逗号运算符。
- 建议。常量表达式的计算不应导致无符号整数的回绕。
2.13 副作用
- 必需。初始化列表不应包含持续副作用。
- 必需。表达式的值和他的持续副作用应在允许的计算顺序下保持相同。
- 建议。包含自增或自减的完整表达式应无其他除自增自减外的潜在副作用。
- 建议。不应使用赋值运算的结果。
- 必需。逻辑与和逻辑或的右手操作数不应包含持续副作用。
- 强制。sizeof运算符的操作数不应包含任何有潜在副作用的表达式。
2.14 控制语句表达式
- 必需。循环计数器不应有浮点型数据。
- 必需。for循环应完整。
- 必需。控制语句不应一成不变。
- 必需。if语句和迭代表达式应有布尔类型。
2.15 控制流
- 建议。不应该使用goto语句。
- 必需。goto语句应跳跃至同一函数后定义的语句处。
- 必需。被goto语句引用的任何标志都应在同一块内定义或任何包含goto语句的块。
- 建议。不应有多个break或goto语句来结束迭代语句。
- 建议。一个函数在其结尾应该有单一的退出点,即一个函数最多有一个return语句。
- 必需。迭代语句和选择语句的主体应是复合语句。
- 必需。if elseif语句应由一个else语句结束。
2.16 Switch语句
- 必需。所有switch语句都要是完整的。
- 必需。switch标签应仅用于最紧密封闭的复合语句作为switch语句的主体时。
- 必需。每个switch分支都要有一个break语句。
- 必需。每个switch语句都要有一个default标签。
- 必需。default标签应出现在switch语句的第一个或最后一个标签处。
- 必需。每个switch语句都要至少包含两个分支。
- 必需。switch表达式不应有布尔类型。
2.17 函数
- 必需。头文件<stdarg.h>内的功能不可使用。
- 必需。函数不能调用自身,不管是直接或者间接的,即不允许递归调用;
- 必需。函数应被明确定义。
- 必需。所有函数退出位置都应有相应的返回语句,除了返回值为void类型的函数。
- 建议。函数参数中若有数组,在调用函数的时候数组大小应与定义时的数组大小相同。
- 强制。定义数组时不应在[]中包含static修饰的变量。
- 必需。非空返回值的函数的返回值应被使用。
- 建议。函数参数不应被修改。
2.18 指针和数组
- 必需。数组的索引应该是指针数学运算中唯一可允许的方式。
- 必需。只有基类型相同的指针间才可以相减。
- 必需。除非指针指向同一个对象,否则不应使用关系运算符进行比较。
- 建议。不要使用+、+=和-=操作符来应用于指针类型表达式。
- 建议。不要定义两级以上的指针。
- 必需。具有自动存储功能的对象的地址不得复制到第一个对象停止存在后仍然存在的另一个对象。
- 必需。数组定义时必需指定大小。
- 必需。变长数组不应使用。
2.19 重叠存储
- 强制。一个对象不得分配或复制到一个重叠的对象。
- 建议。union关键字不应被使用。
2.20 预处理指令
- 建议。#include指令前应只存在预处理器指令或注释。
- 必需。头文件名中不应包含‘,’和注释字符。
- 必需。#include后应跟随
或“filename”。 - 必需。宏名不应与关键字冲突。
- 建议。不要使用#undef。
- 必需。宏的参数中不应出现类似预处理指令的符号。
- 必需。由宏参数扩展产生的表达式应用括号括起来。
- 必需。#if或#elif预处理指令的控制表达式应赋值为0或1.
- 必需。#if或#elif中控制表达式使用的标识符在赋值前应被#define。
- 建议。#和##预处理器操作符不应使用。
- 必需。宏参数在紧随#后后续不得有##紧跟该参数。
- 必需。宏参数用作#或##操作符的操作数,这本身就是收到进一步的宏替换,应仅被用作这些操作符的操作数。
- 必需。以#开头的一行应是有效的预处理指令。
- 必需。所有的#else、#elif和#endif预处理指令应该同与他们相关的#if或者#ifdef指令放在相同的文件中。
2.21 标准库
- 必需。#define和#undef不得用于保留标识符或保留宏的名字。
- 必需。不应定义保留的标识符或宏名。
- 必需。<stdlib.h>的allocation和deallocation函数不应使用。
- 必需。不应使用标准头文件<setjmp.h>。
- 必需。不应使用标准头文件<signal.h>。
- 必需。标准库中的输入输出函数不应使用。
- 必需。不应使用<stdlib.h>的atof,atoi,atoll函数。
- 必需。不应使用<stdlib.h>的abort,exit,getenv和system函数。
- 必需。不应使用<stdlib.h>的bsearch函数。
- 必需。不应使用标准库中的时间和日期函数。
- 必需。不应使用标准头文件<tgmath.h>。
- 建议。不应使用<fenv.h>中的异常处理功能。
2.22 资源
- 必需。用标准库函数动态获得的资源应显式释放。
- 强制。仅用标准库函数分配的内存需要被释放。
- 必需。同一文件不应在同一时间在不同流中读写。
- 强制。不应尝试在以只读形式打开的流中写入数据。
- 强制。指向文件对象的指针不应被引用。
- 强制。在相关流关闭后文件指针的值不应使用。