关于MISRA-C编程规范的一些思考
1: 对语言子集的理解
编程语言定义了这个语言的全部特性,但是这些特性并不都是安全的。例如C语言中的指针:C语言本身并没有规定一定不能读取或者写入哪个地址。虽然一些编译器可能会对指针的访问作出限制或者提示,但这并不是语言本身的定义。那么,通过限制对语言部分特性的使用,可以有效降低由不恰当使用这些特性带来的质量风险,也就是所谓语言子集的概念。
典型的功能安全标准也规定了对语言子集的使用,以IEC61508为例:
2: 强制规则是否一定要遵守
答案是否定的。MISRA-C允许对强制规则进行所谓的背离,只是如果发生了背离,需要提供相关的描述和认可。其实这也是可以理解的。因为MISRA-C中的部分规则限定过于严苛,如果完全遵守,那写出来的代码可能看起来非常别扭,或者干脆就没法写。本文将在下面给出一些实例。
3: 对规则14.7的理解
规则 14.7 (强制) : 一个函数在其结尾应该有单一的退出点。
这一条和某些功能安全标准上倡导的“防御式编程”是冲突的。防御式编程,讲究的是对异常情况进行早期判断,并及时进行错误处理或者退出执行过程,一种常见的方式是:
int Func(int *p1, int *p2)
{
if (NULL == p1)
{
/* TODO: 日志记录或者错误处理 */
return -1;
}
if (NULL == p2)
{
/* TODO: 日志记录或者错误处理 */
return -1;
}
/* ......... */
return -1;
}
如果遵循14.7 ,那么这个函数的写法就要换成一层层判断嵌套的方式,它的可读性和可维护性就会差很多。所以,14.7这个规则,在实际应用上,往往是背离的。
4: 对规则16.1的理解
规则 16.1 (强制):函数定义不得带有可变数量的参数。
这条规则说的是函数不能使用变参。但是,在嵌入式开发领域中,变参有时候是不可避免的。
上面是AutoSar-CP标准体系中,对操作系统的定义。其中就有部分接口使用了可变参数。当然这里在开发的时候也要记录为背离。
5: 对规则11.2的理解
规则 11.2 (强制): 对象指针和其他除整型之外的任何类型指针之间、对象指针和其他类型对象的指针之间、对象指针和 void 指针之间不能进行转换。
最后一条“对象指针和 void 指针之间不能进行转换”:个人认为这一条不能照本宣科。因为void为C语言提供了一定的“泛型”能力,这使得上层软件可以拥有良好的硬件可移植性,例如:
void *memset (void *buffer, int c, size_t num);
如果对象指针和 void 指针之间不能进行转换,那么就必须显示定义memset中缓冲区的数据类型,这就显著降低了这个接口在不同硬件之间的可移植性。