MISRA C编写更安全清晰的C代码

电子说

1.2w人已加入

描述

嵌入式开发人员经常抱怨没有一种编程语言能完美满足他们的特定需求。在某种程度上,这种情况并不令人惊讶,因为尽管有很多开发人员在开发嵌入式应用程序,但他们仍然只是世界编程社区的一小部分。然而,一些语言在开发时就考虑到了嵌入式。值得注意的例子是 PL/M、Forth 和 Ada,它们都已被广泛使用,但从未被普遍接受。其他语言,如 Rust,正在获得支持,但尚未成为主流。几乎被普遍采用的折衷方案是 C。如何才能使这种折衷方案最有效地发挥作用?

C 语言简洁、富有表现力且功能强大。它为程序员提供了编写高效、可读和可维护的代码的方法。所有这些功能都说明了它的受欢迎程度。不幸的是,该语言还使粗心的开发人员能够编写危险的、不安全的代码,这些代码可能会在开发项目的所有阶段和部署中导致严重的问题。对于安全性和/或安全性是主要优先事项的应用程序,语言的这些缺点是一个主要问题。

正是在这种背景下,在 1990 年代后期,汽车工业软件可靠性协会 (MISRA) 推出了一套在车辆系统中使用 C 的指南,即后来的 MISRA C。从那时起,该指南一直在稳步推进。完善,不时发布更新。还建立了使用 C++ 的类似方法。尽管该指南最初针对的是汽车软件开发人员,但很快就意识到它们同样适用于安全至关重要的许多其他应用领域,并且该标准现在已被许多行业广泛采用。

尽管 MISRA C 不是风格指南——事实上,许多用户在应用风格指南和标准的同时——许多规则也促进了清晰、可读、可维护的代码的编写。这是非常有益的,因为易于理解的代码不太可能包含细微的错误或未定义的行为。

MISRA C 的完整详细信息可从https://misra.org.uk获得,并且有许多可用的工具支持该方法。

我将在这里简单介绍一下指南。我的参考资料来自 MISRA C:2012 第三版,第一版。MISRA C 正在不断审查中,增量更改解决了指南的清晰度和准确性以及对新版本 C 语言标准的支持。尽管细节发生了变化,但整体理念和方法没有变化。

规则 13.2 – 在所有允许的评估顺序下,表达式的值及其持续的副作用应相同

C 语言标准在表达式中的求值顺序方面为编译器提供了非常广泛的自由度。因此,任何对评估顺序敏感的代码都是依赖于编译器和依赖于编译器的代码,因此应始终将其视为不安全的。

例如,递增和递减运算符的使用可能会很麻烦:

val = n++ + arr[n];

访问arr的哪个元素程序员是否期望用于索引数组的n的值是在增量之前还是之后?尽管看起来好像在数组索引之前执行了增量,但它假设了左右表达式评估,这不是一个有效的假设。所以,代码不清楚,应该重写如下:

val = n + arr[n+1];
n++;

或者

val = n++;
val += arr[n];

甚至

val = n;
n++;
val += arr[n];

您选择哪个选项取决于个人风格。它们都执行相同的操作,事实上,优化编译器很可能会生成完全相同的代码。

一个表达式中使用的多个函数调用可能会出现类似的问题。函数调用可能具有影响另一个函数的副作用。例如:

val = fun1() + fun2();

在这种情况下,如果任何一个函数都可以影响另一个函数的结果,那么代码就是模棱两可的。要编写安全代码,必须消除任何可能的歧义:

val = fun1();
val += fun2();

现在很清楚fun1()是首先执行的。

规则 17.2 – 函数不得直接或间接调用自己

 

有时,表达算法的一种优雅方式是使用递归。但是,除非对递归进行非常严格的控制,否则存在堆栈溢出的危险,这反过来又会导致非常难以定位错误。在安全关键代码中,应避免递归。

Rule 19.2 – The union keyword should not be used

Although C is a typed language, typing is not very strictly enforced, and developers may be tempted to override typing to “simplify” their code. Adhering to the constraints of data types is essential to create safe code, as any attempts to get around data types can produce undefined results. The union keyword can be used for a number of purposes, which generally result in unclear code, but can also be a means to circumvent typing.

One example would be using a union to “take apart” an unsigned integer, thus:

union e
{
   unsigned int ui;
   unsigned char a[4];
}f;

在这种情况下, ui的每个字节都可以作为 a 的元素访问但是,我们不能确定a[0]是否是最不重要的字节,因为这是一个实现问题。(本质上与处理器的字节序有关。)替代方法可能是使用移位和屏蔽,因此:

unsigned char getbyte(unsigned int input, unsigned int index)
{
  input >>= (index * 8);
  返回输入 & 0xff;
}

有人可能会争辩说,这些规则(以及 MISRA C 的大多数,如果不是全部的话)只是常识,任何优秀的程序员都会采用这种方法。这可能是真的,但一套明确的指导方针让机会更少。

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分