MISRA-C那些事儿

对于某些特定的应用场合,特别是安全性要求更高的场合,为了保证整个系统的安全性,例如汽车电子领域,工程机械等领域。因此,1998年MISRA组织发布了汽车制造业MISRA-C标准来保证该嵌入式领域的编安全,本文将聊聊关于MISRA-C的那些事儿,共同探讨一些编码过程中我们极容易忽视的点儿。

1. 汇编语言应该被封装并隔离;

出于执行效率的考虑,有些时候不得不嵌入简单的汇编指令,比如开关中断,但是无论如何,都应当使用宏定义或者汇编函数来进行合理的隔离,以防止出现任何安全隐患。

#define NOP asm(" NOP");

2.所有#pragma指令的使用应当文档化并给出良好解释;

由于#pragma指令作用于编译器,且不同编译器执行同一条指令有时效果不一致,所以对于#pragma指令的使用应当文档化,以方便进行软件移植。

3. 单纯的char类型只用作存储和使用字符值,而signed char 和unsigned char类型应该只用做存储和使用数字值;(强制)

4. 应当使用指示大小和符号的typedef来替代基本类型;

开发者不应当直接使用char,short,int, long, float等基本类型,不便于软件移植,下面通过列举ISO(POSIX)的typedef,对于32位计算机,如下所示:

5. 外部对象或函数应该声明在唯一的文件中;

通常是在一个头文件中声明一个标识符,而在定义或使用该标识符的任何文件中包含该头文件。一般而言,只有全局变量或者函数才用extern,虽然也可以包含其中一个定义该变量的头文件,但是暴露了所有接口的同时也增加了编译时间,所以如果只需使用某个特定的函数或者变量,建议直接使用external +类型+变量或者函数来实现跨文件使用。

6. 后缀“U”应该用在unsigned 类型的常量上;

整数常量类型的存在是混淆的根源,为了避免此类混淆,提高代码的可读性与清晰性,我们需要对unsigned类型后面都添加后缀“U”,相关注意事项如下:

• 任何带有“U”后缀的值都是unsigned类型;

• 任何不带“U”后缀且小于231的十进制数是signed类型;

• 不带后缀的大于或等于215的十六进制数可能是signed或unsigned类型;

• 不带后缀的大于或等于231的十进制数可能是signed或者unsigned类型;

7. 浮点表达式不能做相等或者不等的检测;

由于浮点类型的固有特性,等值比较通常不会计算为TRUE,即使比较通常不会计算为TRUE,所以为了获得确定的浮点类型比较,建议写个实现比较运算的库,该库应该考虑浮点的粒度(FLT_EPSILON)以及参与比较的数的量级;在面试题中经常会有如何将float类型数据x 与0比较,一般做法如下所示:

const float EPSINON 0.00001;

if((x >= -EPSINON) && (x <= EPSINON))

{

return TRUE;

}

8. for循环中用于迭代计算的数值变量不应在循环体中修改;

9. 不能有不可到达(unreachable)的代码;(强制)

本规则适用于在任何环境中均不会到达或者调用的代码,无论是在逻辑设计层面不可达,还是因为压根就没有调用而遗留在程序中的废弃代码。

10.所有的if...else if 结构应该由else 子句结束;

不管是一条if语句跟后一个或者多个else if语句都需要应用本规则,常见的if ...else组合,应当使用防御性编程defensive proggramming来保证所有情况均考虑在内。

11. 函数不能调用自身,不管是直接还是间接调用;

这就意味着安全系统就不应该使用递归函数进行调用,因为递归本身就可能发生堆栈空间使用过度的危险,这将导致严重的后果,除非递归函数经过严格的测试,否则不可能在执行之前确定什么是最坏情况下的堆栈使用。

12. 函数原型中的指针参数如果不是用于修改所指向的对象,就应该声明指向const的指针;

其中const只限定用于指针所指向的对象,而非指针本身,如下代码所示:

void func(int16_t *par0, const int16_t *par2, int16_t *par3)

{

*par0 = *par2 + *par3;

return;

}

可见par0在函数func中被修改,par2以及par3均未被修改,但是仅仅par2正确添加了const标识符,但是par3没有,所以par3的使用不符合MISRA-C的标准。

13.指针的数学运算只能用于指向数组或者数组元素上,且对象声明的间接指针一般建议不多余两级;

对于指针的增减运算,一定要小心,否则会出现未定义的行为,另外如果间接指针多于两级,那么很容易导致代码可读性变差,不利于后续的软件维护,在此也顺便提一点很多人都容易将指针与引用混淆,下面就简要总结一下指针与引用的区别与联系

联系:两者都代表的是指向对象的地址;

区别:

• 引用必须初始化,而指针不必;

• 引用初始化之后不能改变,而指针可以;

• 可以存在指向空值的指针,但是不能存在指向空值的引用;

14. C的宏只能扩展为用大括号括起来的初始化,常量,小括号括起来的表达式、类型限定符、存储类标识符或者do-while-zero结构;

其中do-while-zero结构是在宏语句体中唯一可接受的具有完整语句的形式,该结构可以用于封装语句序列并保证其是正确的。注意,在宏语句的末尾一定必须省略分号。

#define PI 3.14159F

#define XSTAL 100000

#define CLOCK (XSTAL/16)

#define PLUS(X)((X)+ 2)

#define READ_TIME_32()\

do{\

DISABLE_INTERRUPTS();\

time_now = (uint32_t)TIME_HI << 16;

time_now = time_now|(uint32_t)TIMER_LO;\

ENABLE_INTERRUPTS();\

}while(0) /* example of do-while-zero */

15. 在定义函数宏时,每个参数实例都应该以小括号括起来,除非它们作为#或者##的操作数。

在函数宏的定义中,参数都应该用小括号括起来,例如一个ABS函数可以定义为

#define abs(x) (((x) >=0)? (x): -(x)) /* compliant */

#define abs(x) (((x) >= 0) ? x: -x) /* not compliant */

16. 不能重用标准库宏、对象和函数的名称,同时传递给库函数的值必须检查其有效性;

对于C语言诸多库函数都是根据ISO标准并不需要检查传递给它们的参数的有效性,即使标准要求这样,或者编译器的编写者声明要这么做,也不能保证会做出充分的检查,因此在调用库函数之前务必对输入参数进行相关检查,否则即使库函数自身有自身的参数域检查,但是由于编译器的不同,它们的返回值也可能不同,这对定位问题会产生干扰。

17. 不要使用动态堆的内存分配;

这就禁止了alloc、malloc、realloc和free的使用,总而言之,需要尽量避免此类函数的使用,容易导致内存不一致、内存泄漏、内存碎片过多的问题产生如有些string.h中某些函数的使用到动态堆的分配,也应当避免此类函数的使用。

原文链接:
https://zhuanlan.zhihu.com/p/336595394

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值