1、什么是Misra C规范

2、C语言编程的痛点

3、使用MisraC的优势

4、部分规则解读

背景

1998年汽车工业软件可靠性联合会(MISRA)发布的针对汽车工业软件安全性的C语言编码规范,成为MISRA-C:1998。此编码规范最初只是针对汽车制造业的嵌入式开发,其目的是在增进嵌入式系统的安全性及可移植性。从MISRA-C:2004开始扩大覆盖范围到其他高安全性系统,最新版本为MISRA-C:2012。

目前嵌入式行业里,使用嵌入式编码最多的就是汽车行业了,一辆高端汽车整车的代码行数已经突破了1亿行。那么如果这些代码都写得乱七八糟,会有顾客敢买吗?为了规范大家的写法,增强可读性,可维护性,减少代码可能出现的低级错误,于是MISRA协会制订了Misra C规范,用于规范大家的编程,提高代码整体的可靠性。

MISRA-C 编程规范的推出迎合了很多汽车厂商的需要,因为一旦厂商在程序设计上出现了问题,用来补救的费用将相当可观。 1999 年 7 月 22 日,通用汽车公司( General Motors)就曾经因为其软件设计上的一个问题,被迫召回350 万辆已经出厂的汽车,损失之大可想而知。

随着Misra C规范的知名度越来越高,其他产业也逐渐开始使用Misra C:包括航天、电信、国防、医疗设备、铁路等领域中都已有厂商使用Misra C,成为了全球公认的嵌入式编程规范

什么样的代码是好的代码?

对于程序员来说,能工作的代码并不等于“好”的代码。 “好”代码的指标很多,包括易读、易维护、易移植和可靠等。其中,可靠性对嵌入式系统非常重要,尤其是在那些对安全性要求很高的系统中,如飞行器、汽车和工业控制中。这些系统的特点是:只要工作稍有偏差,就有可能造成重大损失或者人员伤亡。一个不容易出错的系统,除了要有很好的硬件设计(如电磁兼容性),还要有很健壮或者说“安全”的程序。

然而,很少有程序员知道什么样的程序是安全的程序。很多程序只是表面上可以干活,还存在着大量的隐患。当然,这其中也有 C 语言自身的原因。因为 C 语言是一门难以掌握的语言,其灵活的编程方式和语法规则对于一个新手来说很可能会成为机关重重的陷阱。同时,C 语言的定义还并不完全,即使是国际通用的 C 语言标准,也还存在着很多未完全定义的地方。要求所有的嵌入式程序员都成为 C 语言专家,避开所有可能带来危险的编程方式,是不现实的。最好的方法是有一个针对安全性的 C 语言编程规范,告诉程序员该如何做。

关于编码规范:

很多的大公司都有自己相应的代码编程规范。针对C语言尤其严谨,比如比较著名的有:华为C语言编程规范、阿里C语言编程规范和MisraC等。但是侧重点又各有不同,各类规范可能会有交集,也有自己独特的地方存在,但是大家都是致力于让程序员能写出一手更好的代码:

华为、阿里的编程规范更加注重代码风格的规范

-比如每行代码不能超过120个字、代码缩进、空格和大括号等的使用,这些是MisraC这样的规范中没有的

MisraC更加注重代码的功能性、稳定性和可维护性

- 比如所有的switch的case里都要有break,以防止错误逻辑的意外发生

- 再比如操作符两边的数据类型需要一致,以防止隐式类型转换会发生与程序员想法不一致的情况(比如uint8类型的a就不能和int16类型的b做">" "<" "=="的比较)

C语言编程的痛点:

1、程序员编码过程的失误

人非圣贤,孰能无过,相信大家都有过将==写成=的经历吧,然后找半天原因,最后无意间发现了这个bug;if 语句的结尾出现的多余分号能完全改变代码逻辑。 =>    If(a>b) c++;          if(a>b); c++;

2、程序员对C语言了解不充分

C语言的内容还是不少的,编码要求我们的代码要100%按要求运行。但是实际程序员编码过程中,最常用的语法无法覆盖100%,有些语法或者用法可能好几年才会用到一次。因此,程序员可能在编码过程中产生对C语言的错误理解,导致逻辑错误。

比如:运算符的优先级,大家能第一时间确认c = a + b << 1中,+和<<哪个先运算吗?为了避免这类问题,MisraC要求在含混不清的代码中加上括号,比如这样:c = (a + b) << 1来防止误解

3、C语言编译器的差异导致程序运行不一致

C语言发展了很多年了,但是仍然无法避免因为编译器的不同而产生最终结果的差异,甚至背离程序员编码的初衷。因此,为了更好的代码移植性和可靠性。MisraC会有很多的规定来限制写法,保证代码在所有编译器上输出的程序都能有相同的运行结果

4、编译器本身的错误

编译器本身也是一个程序,是程序就可能会出错,编译器的编译可能会导致错误。因此减少一些编译器容易出错的写法,也可以提高代码可靠性

5、运行时的错误

C语言本身运行时检查能力比较弱,C 编译器通常不为某些常见问题提供运行时检查,诸如一些栈溢出,数组越界,数学异常(如除零)等错误,并且通常问题发生后很难定位

使用Misra C的优势:

MisraC不能100%保证程序不出问题,但是能尽可能的预防,使用Misra C具有以下五个维度的优势:

提升可靠性

提升可读性

提升可移植性

提升可维护性

提升安全性

使用Misra C的负面效应:

可能会牺牲执行效率

可能会牺牲代码空间

例如misra c:2004明确指出:不得使用联合体。这是因为,在联合体的存储方式(如位填充、对齐方式、位顺序)上,各种编译器的处理可能不同。

比如,经常会有程序员这么做,一边将采集得到的数据按照某种类型存入一个联合体,而同时又采用另外一种数据类型将该数据读出,如下面这段程序,Misra c推荐用下边的方法来做,这将牺牲执行效率:

更推荐写成:

大家先来看两个问题,把预期结果记录一下,答案之后再做揭晓

问题1

uint8_t port = 0x5a;

uint8_t result_8;

result_8 = (~port)>>4;

问题2:

uint16_t a = 10;

uint16_t b = 65531;

uint32_t c = 0;

uint32_t d;

d = a + b + c;

跨越数据类型的重重陷阱

三种隐式类型转换:

1、整数提升转换

当对小于int的整数类型执行操作时,将提升它们,如果该原始类型的所有值都可以用int类型来表示,那么该小类型(比int小的类型,如char、short、bit-field和enum)将会转换为int的类型。否则的话,它将被转换为unsigned int。整数提升作为通常算术转换的一部分,常用的算术运算符号:一元运算符(+,-,~,以及位操作符& |)。

下面的代码片段展示了该整型提升

char c1 = 1,c2 = 3;c2++;c1 = c1 + c2;

整型提升的好处是,避免了中间值的溢出,如下

char result,c1,c2,c3;c1 = 100;c2 = 3;c3 = 6;result = c1 * c2 / c3;

如上代码char类型最大值为255,c1*c2的结果值(300)无法被表示,由于整形提升原则,他们的值就可以被保存,最终结果转为char,不会被丢失

2、赋值转换

(1)赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给他。

(2)函数调用中参数传递时,系统隐式地将实参转换为形参的类型后,赋给形参。

(3)函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。

3、平衡转换

算术运算式中,低类型能够转换为高类型。相关的算术操作符有乘除( */%)、加减(+ -)、位操作(&|^)、条件操作符(..?..:..)、关系操作符(>、>=、<、<=)、等值操作符(==、!=),平衡转换总是涉及到两个不同类型的操作数,其中一个需要进行隐式转换。

例如:

char a=1;Long b=12345678;int c;c = a + b;

则a会被扩充为与b相当的长整形,再执行“+”运算,所得结果赋给c(包含隐式的截断操作。)

回到之前的问题1

uint8_t port = 0x5a;

uint8_t result_8;

result_8 = (~port)>>4;

代码的本意是先对port取反,然后向右移动四位,期望的结果是0x0a,但是,操作过程中,port首先会升级为signed int类型,然后会产生下面图表中的结果。

Misra C 规则 10.5(强制): 如果位运算符 ~ << 应用在基本类型为 unsigned char unsigned short 的操作数,结果应该立即强制转换为操作数的基本类型。

问题1按此规则修改后,可以得到期望的结果0x0a

result_8 = (uint8_t)(~port)>>4;

回到之前的问题2

uint16_t a = 10;

uint16_t b = 65531;

uint32_t c = 0;

uint32_t d;

d = a + b + c;

如果用一个 32 位的编译器来编译这段程序 ,最终结果是 d = 65541 ,程序员“幸运地”得到了预期的结果。 如果是 16 位的编译器 ,得到的结果却是 d = 5 。

由于“ + ”运算是左结合的 ,所以 d = a + b + c 等效于d = (a + b) + c , 即先执行 a + b ,所得的和再与 c 相加 ,最后结果赋值给 d。 问题就出在 a + b 这个中间步骤中。 由于a 和 b 都是 16 位整型(注意编译器也是 16 位的) ,故而 a + b的结果也是 16 位整型 ,则 a + b 的值是 0x0005 (有溢出) ;再扩充为 32 位整型 0x00000005 和 c 相加赋值给 d ,d = 5 ,这并非程序员预期的结果。

所以 ,在 16 位编译器中 ,问题 2 的那段代码很可能导致严重错误。 当然 ,如果程序员用() 指定了运算优先级的话 ,即最后一行代码写成 d = a + ( b + c) ,也可以避免上述溢出错误 ,然而 ,这终究不是治本的办法。 只有明确每一个操作数的实际数据类型 ,才能保障代码的安全性。

MISRA C 中对于表达式中存在隐式数据类型转换的情况作了严格的限制。

规则 10. 1(强制) :以下情况下 ,整型表达式中不允许出现隐式数据类型转换。

(1)整型操作数不是被扩充为更多位数的同符号整数;

(2)表达式是复杂表达式;

(3)表达式不是常数表达式 ,且是函数的参数;

(4)表达式不是常数表达式 ,且是函数的返回表达式。

规则解读:以上规则广泛地封装了下列原则

(1)有符号和无符号之间没有隐式转换

(2)整型和浮点类型之间没有隐式转换

(3)没有从宽类型向窄类型的隐式转换

(4)函数参数没有隐式转换

(5)函数的返回表达式没有隐式转换

(6)复杂表达式没有隐式转换

指针的类型转换

规则11.4(推荐):指向不同数据类型的指针之间不能相互转换。

uint8_t *p1;

uint32_t *p2;

P2 = (uint32_t *)p1;

预期是将从p1单元开始的4个字节组成一个32位整形来参与运算,但是某些字节寻址的CPU会要求32位(4字节)整型存放在4的整倍数地址上,假设p1一开始指向的是0x0003单元(对uint8_t型的整型没有对齐要求),则执行上述代码最后一行强制转换后,p2到底指向哪个单元就无法预料了。。

规则11.5(强制):指针转换过程中不允许丢失指针的constvolatile属性。

uint16_t x;

uint16_t* const cpi=&x;/*const指针*/

const uint16_t *pci;/*指向const整型的指针*/

volatile uint16_t *pvi;/*指向volatile整型的指针*/

uint16_t *pi;

pi = cpi;/*此指针转换是允许的*/

Pi = (uint16_t *)pci;/*非法指针类型转换,丢失const类型*/

Pi = (uint16_t *)pvi;/*非法指针类型转换,丢失volatile类型*/

规则17.1(强制):只有指向数组的指针才允许进行算术运算,

规则17.2(强制):只有指向同一个数组的两个指针才允许相减

规则17.3(强制):只有指向同一个数组的两个指针才允许用>,<=,<,<=等关系运算符进行比较

规则17.4(强制)允许用数组索引做指针运算

uint8_t a[10];

uint8_t *p;

p = a;

*(p+5)=0;/*不允许*/

a[5] = 0;/*允许*/

尽管就这段程序而言,二者等价。

防范表达式的失控

规则 12. 1 (推荐) :应该减少表达式对 C 语言运算符

优先级的依赖性

if (STA TRegister & BUSYMask == 0) {

do_something;

}

该程序的本意是读状态寄存器 ( STA TRegister) 的值 ,如果其 BUSY 位是 0,则做某些操作。 但是 ,由于判等运算符“ = = ”的优先级高于位与操作符“ &” ,实际的判断表达式变成了

“( STA TReg2ister & (BUSYMask = = 0) ) ! = 0”。

此时应该加入括号“() ”以保证判断表达式的正确性 :

if ( (STA TRegister & BUSYMask ) = = 0) {

do_something ;

}

规则 12. 4 (强制):逻辑运算 ( & & | | ) 的右操作数不允许包含副作用

if (istrue | | do_something_with_side_effects () ) {

do_something ;

}

如果 istrue 非 0 ,编译器认为表达式的值已经确定为真 ,从而不再进行后面的求值 ,于是有副作用的操作被忽略 ,影响了后继操作。建议单独允许表达式取得返回值,仅将变量做逻辑运算判断而不是表达式。

value1 = expression1;

value2 = expression2;

if(value1 || value2){

do_something;

}

精确的程序流控制

规则 13. 1 (强制):赋值表达式不能用在需要布尔值的地方

if(x == y){
foo();
}

if(x = y){
foo();
}

下边的代码首先将 y的值赋给 x ,然后判断 x 是否为 0 ,如果不为 0 ,调用 foo ()函数。 这两段代码只相差一个等号 ,却使判断条件大不相同 ,程序的执行流程会出现很大差别。

按照misra c的规则,上述下边代码应该这么写

uint8_t x ,y ;
x = y ;
if (x  !=  0) {
foo ( ) ;
}

这样 ,当看到需要布尔值的地方出现了赋值表达式 ,就可以立即判断这是一个错误。

规则 14. 1 (强制): 不能存在无法执行到的代码。

规则 14. 9 (强制):if 结构后面必须是一个复合语句 (即用大括号包含) ,else 后面必须是一个复合语句(即用大括号包含) 或者另一个 if 语句

规则 14. 10 (强制):if . . . else if 结构必须由一个 else 子句结束

规则 15. 1 (强制): switch 语句的主体必须是复合语句(用大括号包含)

规则 15. 2 (强制):所有非空的 switch 子句都应该用 break 语句结束。

规则 15. 3 (强制):switch 的最后一个子句必须是 default 子句 ,如果 default 中没有包含任何语句 ,那么应该有注释来说明为什么没有进行任何操作。

构建安全的编译环境

规则 8. 1 (强制):函数必须声明原型 ,在函数定义和调用时原型必须可见。

规则 19. 7 (推荐) :应优先考虑使用函数而非函数式宏定义。

规则 19.10(强制): 在定义函数宏时,每个参数实例都应该以小括号括起来,除非它们做为###的操作数

# define abs (x) (x > = 0) x : - x

显然,程序员希望求变量 x 的绝对值。那实际下面的调用结果呢 ?

abs (a - b) ;  展开后为(a - b > = 0) a - b : - a - b 。

显然这里的 - a - b并不是程序员想要的结果 ,原因是变元 x 两边没有加小括号。 那么在 x 加上括号以后呢 ?

# define abs (x) ( (x) > = 0) (x) : - (x)

如果此时是abs (a) + 1 ;展开后是(a > = 0) a : - a + 1 ,也不是我们想要的结果。看来还要把整个宏定义加上括号 ,这样才能得到安全可靠的宏定义 :  # define abs (x) ( ( (x) > = 0) (x) : - (x) )

规则 19. 15 (强制)应该采取防范措施以避免一个头文件的内容被包含两次。

多次包含一个头文件可以通过认真的设计来避免。如果不能做到这一点,就需要采取阻止头文件内容被包含多于一次的机制。通常的手段是为每个文件配置一个宏;当头文件第一次被包含时就定义这个宏,并在头文件被再次包含时使用它以排除文件内容。

例如,一个名为“ahdr.h”的文件可以组织如下:

#ifndef AHDR_H

#define AHDR_H

/* The following lines will be excluded by the preprocessor if the file is included

more than once */

#endif

其他:

Misra C扫描软件

Helix QAC(原名PRQA)-提供编码规则检查、数据流分析和代码度量分析等全面的代码静态分析功能,可以自动检测软件中不规范的、不安全的、不明确的、不可移植的有关编码风格、命名惯例、程序逻辑、语法和结构的代码。

Misra c符合度

通过扫描软件,可生成报告,展现软件工程的总体misra c符合度,以及单个文件的符合度。对于不符合规则的根据提示进行排查修改。

Misra c背离说明

对于一些不可避免的或者修改特别复杂,修改可能会带来新的风险的规则,在明确不符合该规则,软件也是安全的情况下,允许写背离说明,澄清其不符合的原因及其安全性。

Misra c规则简介相关推荐

  1. 【转载】MISRA C-2012规则中文版

    原文地址:MISRA C-2012规则中文版_WJKING3的博客-CSDN博客_misra c 一.简介 MISRA C是由汽车产业软件可靠性协会(MISRA)提出的C语言开发标准.其目的是在增进嵌 ...

  2. Altium Designer系列:PCB元件封装库命名规则简介

    说明: 本文原创作者『Allen5G』 首发于微信公众号『Allen5G』 标签:编程,软件,算法,思维 QQ技术资料群:736386324 个人微信  :  coderAllen (人较多,请备注公 ...

  3. ST芯片命名规则简介

    一,简介 本文简单介绍下,如何通过ST主控MCU的芯片型号来知晓芯片所包含的资源. 二,命名规则介绍 三,总结 通过上述图片可以知道ST芯片每个值代表的含义,从而知晓芯片本身所带的资源.

  4. 郑码输入法 编码规则简介 汉字分解

    去找了一本<郑码输入法手册>,然后开始了郑码的学习.这一部分主要是对郑码的基本编码规则做一个了解,然后学习郑码中汉字的分解方式. 一. 认识郑码输入法 1. 简介 郑码输入法是一种较为流行 ...

  5. hive桌游中文规则_桌游--卡卡颂(Carcassonne)中文规则 简介

    游戏人数:2-5 游戏时间:约60分钟 心机程度:★★☆☆☆ 桌游简介: 卡卡颂(Carcassonne)是法国南部靠近地中海与西班牙边境的一个中世纪城镇,该城镇是为著名的旅游城市,以古罗马及中世纪堡 ...

  6. 浅说 Rewrite规则简介

    1.Rewirte主要的功能就是实现URL的跳转,它的正则表达式是基于Perl语言.可基于服务器级的(httpd.conf)和目录级的(.htaccess)两种方式.如果要想用到rewrite模块,必 ...

  7. MISRA C指导指南解读系列4(MISRA C规则20-32)

    2.1.            声明和定义 20.所有的对象和函数标识符在使用前必须声明(R) 21.内部标识符不能与外部标识符具有相同的名称,从而隐藏的外部标识符(R) 例如 static int ...

  8. 自定义 Lint 规则简介

    2019独角兽企业重金招聘Python工程师标准>>> 上个月,笔者在巴黎 Droidcon 的 BarCamp 研讨会上聆听了 Matthew Compton 关于编写自己的 Li ...

  9. MySQL 排序规则简介

    在日常工作中,对于MySQL排序规则,很少关注,大部分时候都是直接使用字符集默认的排序规则,常常忽视了排序规则的细节问题,了解排序规则有助于更好的理解MySQL字符比较和排序相关的知识 简述 说起排序 ...

  10. supports_CSS的@supports规则简介(功能查询)

    supports The two general approaches to tackling browsers' uneven support for the latest technologies ...

最新文章

  1. 马云口中的“计划经济”其实是一种大数据和人工智能
  2. 超好用的免费文献管理软件Mendeley 简易入门教程
  3. 关于51CTO博客大赛的感想
  4. logrotate切割日志rsyslog不重新上传,清空归零iCurrOffs
  5. md5加密解密代码_Python内置方法实现基于秘钥的信息加解密
  6. 蓝桥杯第八届省赛JAVA真题----分巧克力
  7. 新版二开cp盲盒小纸条月老小程序源码
  8. 计算机教室要配备空调吗,教室里不安装空调吗?如何正确安装空调?
  9. Got a packet bigger than 'max_allowed_packet' bytes(mysql)
  10. 吴恩达教授机器学习笔记【一】- 线性回归(2)
  11. Car2go 的前端框架选择
  12. tomcat 7服务器跨域问题解决
  13. java语法分析器_JavaCC语法分析器
  14. w7计算机应用放大按键,Win7窗口最大化和最小化快捷键是什么
  15. Steam 游戏服务器IP地址段
  16. PDF文件中的图片导出
  17. 【TensorFlow】使用slim从ckpt里导出指定层的参数
  18. Windows桌面美化(壁纸网站,任务栏透明、颜色设置)
  19. 大数据时代来临了,你需要了解什么是大数据
  20. 假如你想成为全栈工程师…

热门文章

  1. 网上购物系统-关系型数据库设计举例
  2. excel使用教程_火遍全球的14个Excel学习网:大神套路、视频课、软件下载应有尽有...
  3. Ubuntu18.04 安装 rabbitvcs svn 图形化客户端
  4. 忍痛分享五款小众软件,点赞收藏加关注
  5. Android事件机制深入探讨(一)
  6. Mac 下制作win7启动U盘启动PE
  7. 导入数据java生成逆向sql,用于回滚,你试过吗?
  8. Pos58打印程序开发相关
  9. 什么是生成式对抗神经网络GAN
  10. 形式语言与自动机第一课