Chinaunix首页 | 论坛 | 博客
  • 博客访问: 412965
  • 博文数量: 44
  • 博客积分: 4980
  • 博客等级: 上校
  • 技术积分: 1035
  • 用 户 组: 普通用户
  • 注册时间: 2007-01-09 20:39
个人简介

偶尔编程的胖子 40岁之后还能坚持学习

文章分类

全部博文(44)

文章存档

2023年(12)

2022年(2)

2011年(1)

2010年(6)

2009年(1)

2008年(22)

我的朋友

分类: C/C++

2010-01-06 22:06:16

MISRA C :让嵌入式系统更加安全可靠的一些关键规则
作者: Anders Lindgren, 高级研发工程师 , 瑞典 IAR 公司

  
  没人能否认嵌入式应用正变得越来越普遍。但是,当系统越来越复杂,而我们赋予系统越来越多的责任的时候,我们将必须面对一些重要问题。其中最重要的问题是:我们如何才能确保嵌入式系统是安全可靠的?
C 编程语言为用户提供了许多强大的功能,几乎与汇编语言一样强大。此外, C 语言还包含了一些在标准中未明确定义的区域和不用定义就能实现的条款。
用 C 来编写一个安全可靠的程序并不仅仅意味着该程序能完全按照编程者的预期来运行。它被移植到一个不同的环境下也需要正确运行。更重要的是,当其他人员阅读这些源代码时,它们必须像水晶一样清晰明了:一方面简化了不必要的猜测和诠释,另一方面当基于这些代码来做进一步开发时,能规避诱发新错误的风险。
汽车工业软件可靠性协会 (MISRA) 采取的措施是定义一套 C 编程语言的子集。如果在应用开发中仅使用这个子集,那么许多 C 编程语言中存在的缺陷就能被规避了。
这个子集名称为 "MISRA C" ,在一本名为“ Guides For The Use Of The C Language In Vehicle Based Software ”的手册中有具体定义。该指南共包括 127 条规则,分为“必选”和“建议”两类。
IAR Embedded Workbench 中几款产品(参见以下列表),内建了一个能检查绝大多数 MISRA C 规则的检查器,但是某些规则是无法被自动检测的。
如果在编写应用时忘了使用这些规则,以后再去修改应用以符合这些规范将非常困难。比如, MISRA C 第 118 条规则禁止使用动态分配内存,第 101 条规则规定禁止使用 指针运算 ,第 102 条规则表示不可以使用 超过两级的间接指针 。
另一方面,有几条规则是非常简单明了和成熟,即使你没有想过让你的应用做到 100% 的 MISRA C 兼容,如果能支持其中几条也至少能让应用更可靠一些。
在这篇文章里,我将推荐一些应该适用于所有项目的规则,即便你没想做到 100% 的 MISRA C 兼容。这些被挑选出来的规则通常是编译器不会去检查的。
在 IAR Embedded Workbench 中使用 MISRA C
在 IAR Embedded Workbench 中, MISRA C 的界面非常简单易用,你可以直接选择你所希望检查的规则,也可以在 "MISRA C" 标签下的 " 通用选项 " 部分的 "Options" 菜单列表中进行选择 。如果你使用命令行方式,你可以使用 --misrac 和 --misrac_verbose 命令选项。
大多数规则都由编译器自身来检查。然而,一些规则考察进入到整个应用才能检查,在这种情况下,链接器将执行检查。你可以分别通过列表文件和映射文件来察看被允许使用的规则和检查过的规则列表。请注意,有些规则是不能被自动检查到的。
规则 13 ( 建议使用 )
该条规则声明: 应用程序不应直接使用 char, int, float,  等基本数据类型,而应在特定的编译器中专门 typedef 自己定义特定长度的类型名称,并将 typedef 所定义的类型使 用到代码当中去。
原因是不同的编译器对基本类型会使用不同的底层表达方式。最常见的例子是 int 类型在某个编译器中被当作 16 位处理,而在另一个编译器中被当作 32 位处理。
根据个人经验,我一般会仔细选择定长类型的大小和符号。当我需要表示一个不可能为负数的东西时,我会选择使用 uint16_t ,而不是简单的用 int 表示。这样写代码的好处之一是代码很容易被读懂和理解,读者无须考虑该表达式为负数的情况。
1999 年版本的 C 标准包含的头文件 stdint.h 中,提供了 int8_t 和 uint32_t 形式的类型名称。 IAR Embedded Workbench 在 DLib 标准库中支持这一头文件。
规则 23 ( 建议使用 ): 具有文件范围作用域的所有对象声明都尽量定义成静态的。
正如你可能知道的那样,在应用程序中定义的静态变量和函数是无法被其他模块直接使用的。
该规则:
•  防止你无意间误用内部帮助函数和文件内部的变量
•  促使你认真设计新模块之间的接口
•  使程序接口更加清晰明了是一件很重要的事情,因为当时间久了之后,当初写代码的人未必能够继续留在那里维护应用程序。
使用 IAR Embedded Workbench 时,链接器会在整个应用范围内检测这条规则。
规则 33 ( 必需使用 ): 操作符 "&&" 和 "||" 的右侧表达式不得包含副作用
你可以用 C 语言写出如下代码:
if ((x == y) || (*p++ == z))
{
/* do something */
}
在这个例子中,仅当 || 操作符左边的表达式为假 —- 即 x 和 y 不相等时,才对右边的表达式进行求值(副作用就此产生)。在上述例子中,所谓的副作用就是增加了指针 p 的值。
虽然在标准中已经详细说明了该种行为,但在写代码的过程中还是容易犯下这种错误。即使写代码的人设法正确了解了这种行为,然而每个试图阅读代码或维护代码的人员也仍必须理解这些规则和程序员的意图。
以上代码可以按照另一种更为简洁明了的顺序重新编写:
int doSomething = 0;
if (x == y)
{
doSomething = 1;
}
else if (*p++ == z)
{
doSomething = 1;
}
if (doSomething)
{
/* do something */
}
规则 59 ( 必需使用 ): "if", "else if", "else", "while", "do ... while" 和 "for" 语句块必须用 {} 括起来
基本上来说,你必须整理好思路,不要随便的写出如下例中所示的 else 子句。
if (x == 0)
{
y = 10;
z = 0;
}
else
y = 20;
这条规则意在避免一个经典错误。在下面的例子中,另外增加了一行“ z = 1 ; ”。这行语句看上去是属于 else 子句的一部分,但其实并不是这样。事实上,这句语句紧接在全部的 if 语句块之后,这意味着该赋值将总是发生。

如果一开始就能够将 else 子句用大括号括起来,就不会产生这样的问题了。
if (x == 0)
{
y = 10;
z = 0;
}
else
y = 20;
z = 1;
阅读(1023) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~