【还没看,先转走】MISRA 2004规则

Jerry整理

出处:汽车工业软件可靠性联会

最后更新时间:2005-7-20

转载请注明:来自Sawin系统分析之窗

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的基础上适当修改就可以形成自己的规范。当然其中也有一些过于严苛的东西,这就需要各个开发部门灵活处理了。我个人的体会,编码规范虽然很简单,但是要完全执行,不折不扣,需要开发部门有很高的组织性和纪律性,并且有很好的代码评审机制。因此,如果能够严格地遵守编码规范,本身就是一个开发部门实力的证明。

内容

<环境>

Rule1.1(强制):所有的代码应该遵守ISO 9899:1990“Programming Language C”

Rule1.2(强制):只有当具备统一接口的目标代码的时候才可以采用多种编译器和语言

Rule1.4(强制) 检查编译器/连接器以确保支持31一个有效字符,支持大小写敏感

<语言扩展>

Rule 2.1(强制):汇编语言应该封装起来并且隔离:

例如:#define NOP asm(“  NOP”)

Rule 2.2(强制) :源代码只能采用/*…*/风格的注释

Rule2.3(强制): 字符序列/*不能在注释中使用

注:C语言不支持注释的嵌套即使一些编译器支持这个语言扩展

Rule 2.4(建议):代码段不能注释掉

注:应采用#IF 或者#ifdef来构成一个注释,否则代码里如果有注释会改变代码的作用

<文档化>

Rule 3.3(建议):编译器对于整数除法运算的实施应该写入文档

例编译器:-5/3 = -1 余-2 有些编译器结果是-2于+1

<字符集>

Rule 4.1(强制):只能使用ISO标准定义的字符集

<标识符>

Rule6.5 (强制):在内部范围的标识符不能和外部的标识符用同样的名字,因为会隐藏那个标识符

例:

int16_t i:

Void f()

{

    int16_t i;

    i=3;

}

Rule 5.2(强制): typedef 名称只能唯一,不能重复定义

Rule 5.4(强制): 标记名应该是唯一的标识符

Rule 5.7(建议):标识符不能重复使用

<类型>

Rule 6.1(强制):Char类型只能用来存储使用字符

Rule 6.2(强制):signed和unsigned char 只能用来存储和使用数据值

Rule6.3(建议)对于基本的类型使用Typedef来表示大小和有无符号

例:

Typedef char char_t

Typedef signed int int32_t 

<约束>

Rule 7.1(强制):不要用八进制数

注:整型常数以”0“开始会被认为是8进制

例:code[1]=109

        code[2]=100

        code[3]=052

        code[4]=071

如果是对总线消息初始化,会有危险

<声明和定义>

Rule 8.1(强制):函数都应该有原型声明,且相对函数定义和调用可见

Rule8.2 (强制):无论何时一个对象和函数声明或者定义,它的类型应该明确声明

Rule 8.5(强制):头文件中不要定义对象或者函数

Rule8.3(强制):每个函数声明中的参数的类型应该和定义中的类型一致

Rule 8.8(强制):外部变量或者函数只能声明在一个文件中

注:一般来讲,声明在头文件中,然后包含在定义和使用的文件中

Rule 8.12(强制):数组声明为外部,应该明确声明大小或者直接初始化确定

例:extern int array2[ ]  /* 违反Rule8.8 */

<初始化>

Rule 9.1(强制):所有变量在使用之前都应该赋值

<数学类型转换(隐式)>

Rule 10.1(强制):整型表达式不要隐式转换为其他类型:

    a)转换到更大的整型

    b)表达式太复杂

    c)表达式不是常数是一个函数

    d)表达式不是一个常数是一个返回表达式

Rule 10.2(强制):浮点数表达式不要隐式转换为其他类型:

    a)转换到更大的浮点数

    b)表达式太复杂

    c)表达式是一个函数

    d)表达式是一个返回表达式

<数学类型转换(明确)>

Rule 10.3(强制):整型表达式的值只能转换到更窄小且是同样符号类型的表达式

Rule 10.4(强制):浮点表达式的值只能转换到更窄小的浮点表达式

<数学类型转换>

Rule 10.6(强制):所有的 unsigned类型都应该有后缀”U“

Rule 11.1(强制):指针不能转换为函数或者整型以外的其他类型

<表达式>

Rule12.2(强制):表达式的值应和标准允许的评估顺序一致

例:

X=b[i] + i++;

不同的编译器给出的结果不一样,b[i]是否先执行?

应:x=b[i];

       i++;

比如:

X=func(i++,i);

Rule12.3(强制):sizeof操作符不能用在包含边界作用(side effect)的表达式上

例:

Int32_t=i;

Int32_t=j;

j=sizeof(i=1234);

表达式并没有执行,只是得到表达式类型int的size

Rule 12.4(强制):逻辑操作符&&或者||右边不能包含边界作用(side effect)

例:

If(ishight) && (x== i++)),如果ishight=0那么i++不会评估

Rule 12.3(建议):++和- -不能和其他表达式用在一个表达式中

例:

U8a=++u8b + u8c--;

<控制语句表达式>

Rule13.1(强制):赋值语句不能用在一个产生布尔值的表达式中

例:

If((x=y)!=0)…

更差:

If (x=y)…

Rule13.3(强制):浮点表达式不应该测试其是否相等或者不相等

Rule13.4(强制):for控制表达式中不要包含任何浮点类型

Rule13.6(强制):数字变量作为for循环的循环计数不要在循环体内部被修改

例:

Flag=1;

For(i=0;(i<5)&&(flag==1);i++)

{

Flag=0;

i=i+3;

}

<控制流>

Rule 14.1(强制):不要有执行不到的代码

例:

Swich(event)

{

Case www;

          do_wakeup();

          break;

          do_more();

}

Rule 14.4(强制):goto语句不能使用

Rule 14.5(强制):continue不能使用

Rule 14.6(强制):函数应在函数结束有一个出口

Rule 14.7(强制):witch,while,do ...while,for语句体应是一个混合语句(括号)

Rule 14.10(强制):所有if…else if结构都应该由else结束

<Switch语句>

Rule 15.3(强制):switch的最后应是default

Rule 15.4(强制):switch表达式不能使用布尔表达式

例:

Switch(x==0)

{

… …

}

Rule 15.5(强制):每一个Switch语句都应该有一个case

例:

Switch(x)

{

Uint8_t var; /* 违反*/

Case 0:

A=b;

}

<函数>

Rule16.2(强制):函数不能直接或者间接的调用自己

注:safe-related 系统不能用递归,超出堆栈空间很危险

Rule16.8(强制):non-void类型函数的所有出口路径都应该有一个明确的return语句表达式

<指针和数组>

Rule17.1(强制):指针的数学运算只能用在指向数组的地址上

Rule17.3(强制):>,>=,<,<=不能用在指针类型除非指向同一个数组

Rule 17.5(建议):不要用2级以上指针

<结构和联合>

Rule18.4(强制)不要用Union

<预处理指令>

Rule19.1(建议):#include语句的前面只能有其他预处理指令和注释

Rule19.2(建议):#include指令中的头文件名称不能包含非标准的字符

Rule19.5(强制):宏不能在函数体内定义

Rule19.8(强制):类函数宏调用时不能没有它的参数

<标准库>

Rule20.1(强制):标准库中的保留标识符,宏和函数不能定义,重定义,和undefined

Rule20.4(强制):动态内存分配不能使用

注:不能使用:malloc,calloc,free,realloc

Rule20.9(强制):输入输出库(stdio.h)不能用在产生嵌入式系统中

Rule20.12(强制):时间处理函数<time.h>不能使用

<运行时故障>

Rule 21.1(强制):通过使用一下手段确保把运行时故障最小化:

– 静态分析工具/技术

– 动态分析工具/技术

– 编写明确的代码避免运行时错误

“安全第一”的C语言编程规范

作者:清华大学 陈萌萌 邵贝贝

文章来源:单片机与嵌入式系统应用 2006-4-6 17:44:18

编者按: C语言是开发嵌入式应用的主要工具,然而C语言并非是专门为嵌入式系统设计,相当多的嵌入式系统较一般计算机系统对软件安全性有更苛刻的要求。1998年,MISRA指出,一些在C看来可以接受,却存在安全隐患的地方有127处之多。2004年,MISRA对C的限制增加到141条。

  嵌入式系统应用工程师借用计算机专家创建的C语言,使嵌入式系统应用得以飞速发展,而MISRAC是嵌入式系统应用工程师对C语言嵌入式应用做出的贡献。如今MISRA C已经被越来越多的企业接受,成为用于嵌入式系统的C语言标准,特别是对安全性要求极高的嵌入式系统,软件应符合MISRA标准。

  从本期开始,本刊将分6期,与读者共同学习MISRAC。

  第一讲:“‘安全第一’的C语言编程规范”,简述MISRAC的概况。

  第二讲:“跨越数据类型的重重陷阱”,介绍规范的数据定义和操作方式,重点在隐式数据类型转换中的问题。

  第三讲:“指针、结构体、联合体的安全规范”,解析如何安全而高效地应用指针、结构体和联合体。

  第四讲:“防范表达式的失控”,剖析MISRAC中关于表达式、函数声明和定义等的不良使用习惯,最大限度地减小各类潜在错误。

  第五讲:“准确的程序流控制”,表述C语言中控制表达式和程序流控制的规范做法。

  第六讲:“构建安全的编译环境”,讲解与编译器相关的规范编写方式,避免来自编译器的隐患。

   C/C++语言无疑是当今嵌入式开发中最为常见的语言。早期的嵌入式程序大都是用汇编语言开发的,但人们很快就意识到汇编语言所带来的问题——难移植、难复用、难维护和可读性极差。很多程序会因为当初开发人员的离开而必须重新编写,许多程序员甚至连他们自己几个月前写成的代码都看不懂。C/C++语言恰恰可以解决这些问题。作为一种相对“低级”的高级语言,C/C++语言能够让嵌入式程序员更自由地控制底层硬件,同时享受高级语言带来的便利。对于C语言和C++语言,很多的程序员会选择C语言,而避开庞大复杂的C++语言。这是很容易理解的——C语言写成的代码量比C++语言的更小些,执行效率也更高。

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

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

1 MISRAC规范

  1994年,在英国成立了一个叫做汽车工业软件可靠性联合会(The Motor Industry Software Reliability Association,以下简称MISRA)的组织。它是致力于协助汽车厂商开发安全可靠的软件的跨国协会,其成员包括:AB汽车电子、罗孚汽车、宾利汽车、福特汽车、捷豹汽车、路虎公司、Lotus公司、MIRA公司、Ricardo公司、TRW汽车电子、利兹大学和福特VISTEON汽车系统公司。

  经过了四年的研究和准备,MISRA于1998年发布了一个针对汽车工业软件安全性的C语言编程规范——《汽车专用软件的C语言编程指南》(Guidelines for the Use of the C Language in Vehicle Based Software),共有127条规则,称为MISRAC:1998。[Page]

  C语言并不乏国际标准。国际标准化组织(International Organization of Standardization,简称ISO)的“标准C语言”经历了从C90、C96到C99的变动。但是,嵌入式程序员很难将ISO标准当作编写安全代码的规范。一是因为标准C语言并不是针对代码安全的,也并不是专门为嵌入式应用设计的;二是因为“标准C语言”太庞大了,很难操作。MISRAC:1998规范的产生恰恰弥补了这方面的空白。

  随着很多汽车厂商开始接受MISRAC编程规范,MISRAC:1998也成为汽车工业中最为著名的有关安全性的C语言规范。2004年,MISRA出版了该规范的新版本——MISRAC:2004。在新版本中,还将面向的对象由汽车工业扩大到所有的高安全性要求(Critical)系统。在MISRAC:2004中,共有强制规则121条,推荐规则20条,并删除了15条旧规则。任何符合MISRAC:2004编程规范的代码都应该严格的遵循121条强制规则的要求,并应该在条件允许的情况下尽可能符合20条推荐规则。

  MISRAC:2004将其141条规则分为21个类别,每一条规则对应一条编程准则。详细情况如表1所列。

                表1MISRAC:2004规则分类

      

  最初,MISRAC:1998编程规范的建立是为了增强汽车工业软件的安全性。可能造成汽车事故的原因有很多,如图1所示,设计和制造时埋下的隐患约占总数的15%,其中也包括软件的设计和制造。MISRAC:1998就是为了减小这部分隐患而制定的。

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

  MISRAC规范不仅在汽车工业开始普及,也同时影响到了嵌入式开发的其他方向。嵌入式实时操作系统μC/OSII的2.52版本虽然已经于2000年通过了美国航空管理局(FAA)的安全认证,但2003年作者就根据MISRAC:1998规范又对源码做了相应的修改,如将

  if ((pevent->OSEventTbl[y] &= ~bitx) == 0) {

    /*… */

  }

的写法,改写成

  pevent->OSEventTbl[y] &= ~bitx;

  if (pevent->OSEventTbl[y] == 0) {

  /*… */

  }

发布了2.62的新版本,并宣称其源代码99%符合MISRAC:1998规范。

  一个程序能够符合MISRAC编程规范,不仅需要程序员按照规范编程,编译器也需要对所编译的代码进行规则检查。现在,很多编译器开发商都对MISRAC规范有了支持,比如IAR的编译器就提供了对MISRAC:1998规范127条规则的检查功能。

2 MISRAC对安全性的理解

  MISRAC:2004的专家们大都来自于软件工业或者汽车工业的知名公司,规范的制定不仅仅像过去一样局限于汽车工业的C语言编程,同时还涵盖了其他高安全性系统。

      

                图1汽车事故原因分布图

  MISRAC:2004认为C程序设计中存在的风险可能由5个方面造成:程序员的失误、程序员对语言的误解、程序员对编译器的误解、编译器的错误和运行出错(runtime errors)。

  程序员的失误是司空见惯的。程序员是人,难免会犯错误。很多由程序员犯下的错误可以被编译器及时地纠正(如键入错误的变量名等),但也有很多会逃过编译器的检查。相信任何一个程序员都曾经犯过将“= =”误写成“=”的错误,编译器可能不会认为

   if(x=y)

是一个程序员的失误。

  再举个例子,大家都知道++运算符。假如有下面的指令:

  i=3;

  printf(“%d”,++i);

输出应该是多少?如果是:

  printf(“%d”,i++);

呢?如果改成-i++呢?i+++i呢?i+++++i呢?绝大多数程序员恐怕已经糊涂了。在MISRAC:2004中,会明确指出++或--运算符不得和其他运算符混合使用。

  C语言非常灵活,它给了程序员非常大的自由。但事情有好有坏,自由越大,犯错误的机会也就越多。[Page]

  如果说有些错误是程序员无心之失的话,那么因为程序员对C语言本身或是编译器特性的误解而造成的错误就是“明”知故犯了。C语言有一些概念很难掌握,非常容易造成误解,如表达式的计算。请看下面这条语句:

   if ( ishigh && (x == i++))

很多程序员认为执行了这条指令后,i变量的值就会自动加1。但真正的情况如何呢?MISRA中有一条规则:逻辑运算符&&或||的右操作数不得带有副作用(side effect)*,就是为了避免这种情况下可能出现的问题。

*所谓带有副作用,就是指执行某条语句时会改变运行环境,如执行x=i++之后,i的值会发生变化。

  另外,不同编译器对同一语句的处理可能是不一样的。例如整型变量的长度,不同编译器的规定就不同。这就要求程序员不仅要清楚C语言本身的特性,还要了解所用的编译器,难度很大。

  还有些错误是由编译器(或者说是编写编译器的程序员)本身造成的。这些错误往往较难发现,有可能会一直存留在最后的程序中。

  运行错误指的是那些在运行时出现的错误,如除数等于零、指针地址无效等问题。运行错误在语法检查时一般无法发现,但一旦发生很可能导致系统崩溃。例如:

#define NULL 0

   ……

  char* p;

  p=NULL;

  printf(“Location of 0 is %d\n”, *p);

语法上没有任何问题,但在某些系统上却可能运行出错。

  C语言可以产生非常紧凑、高效的代码,一个原因就是C语言提供的运行错误检查功能很少,虽然运行效率得以提高,但也降低了系统的安全性。

  有句话说得好,“正确的观念重于一切”。MISRAC规范对于嵌入式程序员来讲,一个很重要的意义就是提供给他们一些建议,让他们逐渐树立一些好的编程习惯和编程思路,慢慢摒弃那些可能存在风险的编程行为,编写出更为安全、健壮的代码。比如,很多嵌入式程序员都会忽略注释的重要性,但这样的做法会降低程序的可读性,也会给将来的维护和移植带来风险。嵌入式程序员经常要接触到各种的编译器,而很多C程序在不同编译器下的处理是不一样的。MISRAC:2004有一条强制规则,要求程序员把所有和编译器特性相关的C语言行为记录下来。这样在程序员做移植工作时,风险就降低了。

3 MISRAC的负面效应

  程序员可能会担心采用MISRAC:2004规范会对他们的程序有负面影响,比如可能会影响代码量、执行效率和程序可读性等。应该说,这种担心不无道理。纵观141条MISRAC:2004编程规范,大多数的规则并不会对程序的代码量、执行效率和可读性造成什么大的影响;一部分规则可能会以增加存储器的占用空间为代价来增加执行效率,或者增加代码的可读性;但是,也确实存在着一些规则可能会降低程序的执行效率。

  一个典型的例子就是关于联合体的使用。MISRAC:2004有一条规则明确指出:不得使用联合体。这是因为,在联合体的存储方式(如位填充、对齐方式、位顺序等)上,各种编译器的处理可能不同。比如,经常会有程序员这样做:一边将采集得到的数据按照某种类型存入一个联合体,而同时又采用另外一种数据类型将该数据读出。如下面这段程序:

  typedef union{

    uint32_t word;

    uint8_t bytes[4];

  }word_msg_t;

  unit32_t read_word_big_endian (void) {

    word_msg_t tmp;

    tmp.bytes[0] = read_byte();

    tmp.bytes[1] = read_byte();

    tmp.bytes[2] = read_byte();

    tmp.bytes[3] = read_byte();

    return (tmp.word);

  }

  原理上,这种联合体很像是一个硬件上的双口RAM存储器。但程序员必须清楚,这种做法是有风险的。MISRAC:2004推荐用下面这种方法来做:

   uint32_t read_word_big_endian (void) {

    uint32_t word;

    word=((unit32_t)read_byte())<<24;[Page]

    word=word|(((unit32_t)read_byte())<<16);

    word=word|(((unit32_t)read_byte())<<8);

    word=word| ((unit32_t)read_byte());

    return(word);

  }

  先不论为什么这样做会更安全,只谈执行效率,这种采用二进制数移位的方法远远不如使用联合体。到底是使用更安全的做法,还是采用效率更高的做法,需要程序员权衡。对于一些要求执行效率很高的系统,使用联合体仍然是可以接受的方法。当然,这是建立在程序员充分了解所用编译器的基础上的,而且程序员必须对这种做法配有相应的注释。

4 发展中的MISRAC

  MISRAC并非完美,它自身的发展也印证了这一点。MISRAC:2004就去掉了MISRAC:1998中的15条规则。今后的发展,MISRAC仍然要解决很多问题。比如,MISRAC:2004是基于C90标准的,但最新的国际C标准是C99,而C99中没有确切定义的C语言特性几乎比C90多了一倍,MISRAC如何适应新的标准还需要进一步探讨。

  另外,C++在嵌入式应用中也越来越受到重视,MISRA正在着手制定MISRAC++编程规范。读者可以通过访问网站http://www.misra.org.uk了解MISRAC的发展动向。

5 对MISRAC的思考

  嵌入式系统并不算是一个独立的学科,但作为一个发展中的行业,它确实需要有一些自己的创新之处。嵌入式工程师们不应仅仅局限于从计算机专家那里学习相关理论知识,并运用于自己的项目,还应该共同努力去完善自己行业的标准和规范,为嵌入式系统的发展做出贡献。MISRAC编程规范就是一个很好的典范。它始于汽车工程师和软件工程师经验的总结,然后逐渐发展成为一种对整个嵌入式行业都有指导意义的规范。对于推动整个嵌入式行业的正规化发展,MISRAC无疑有着重要意义。

  从另一个角度讲,MISRAC规范也可以看成是嵌入式工程师对软件业的一种完善。嵌入式工程师虽然不是计算机专家,但却对嵌入式应用有着最深刻的了解,将自己在嵌入式应用中的经验和体会贡献给其他行业,也是他们应该肩负的责任。

                    参考文献

1 MISRAC:2004, Guidelines for the use of the C language in critical systems. The Motor Industry Software Reliability Association, 2004

2 Harbison III. Samuel P, Steele Jr. Guy L. C语言参考手册. 邱仲潘,等译. 第5版. 北京:机械工业出版社,2003

3 Kernighan. Brian W, Ritchie. Dennis M. C程序设计语言. 徐宝文,等译. 第2版. 北京:机械工业出版社,2001

4 Koenig Andrew. C陷阱与缺陷. 高巍译. 北京:人民邮电出版社,2002

5 McCall Gavin. Introduction to MISRAC:2004, Visteon UK, http://www.MISRAC2.com/

6 Hennell Mike. MISRA CIts role in the bigger picture of critical software development, LDRA. http://www.MISRAC2.com/

7 Hatton Les. The MISRA C Compliance Suite—The next step, Oakwood Computing. http://www.MISRAC2.com/

8 Montgomery Steve. The role of MISRA C in developing automotive software, Ricardo Tarragon. http://www.MISRAC2.com/

你可能感兴趣的:(MISRA)