没有函数指针的状态机

State Machine with no function pointer

我已经为安全 SIL 4 系统实现了一个复杂的状态机,其中包含许多状态转换。 此实现的主干是使用函数指针完成的。 当一切进展顺利时,V&V 反对在 SIL 4 系统中使用函数指针。参考- Rule 9 NASA.Misra C 2004 然而并没有说不能使用函数指针。

有没有其他方法可以在没有任何函数指针的情况下实现复杂的状态机?

首先,那份 NASA 文件不是正典。首先询问哪个 law/directive/standard/requirement/document 强制您遵循 NASA 文档。如果它没有在任何地方强制执行(这似乎很有可能,即使在 NASA 本身),你也没有义务遵守它,你可以忽略整个事情。

如果不能将废话视为废话,您可以在与安全标准撞墙时使用通常的程序:解决方案始终是详细记录规定的规则如何没有意义,并在面对自己的方法论。

因此,与其放弃函数指针,不如通过下面描述的方法确保以安全的方式使用它们。


由于所有 safety-related 设计都归结为风险评估,您将始终拥有:

Error -> Cause -> Hazard -> Safety measure

根据 NASA 文件中给定的(糟糕的)理由,您可以用类似以下内容来证明安全措施 "avoid function pointers" 的合理性:

  1. Wrong code executed -> Corrupt function pointer -> Runaway code/illegal op code

  2. Stack overflow -> Function pointer recursion -> Memory corruption

  3. Confused programmer -> Function pointer syntax -> Unintended program functionality

这一切都相当模糊,而且是一个有问题的风险评估,但这就是 NASA 文件归结为的内容。

对于上面列出的 3 种危害,我建议使用以下安全措施代替 "avoid function pointers":

  1. Defensive programming and assertions.
  2. Defensive programming and assertions. Educate programmers.
  3. Use typedef. Educate programmers.

防御性编程和断言

  • 对于状态机,确保状态数(枚举中的项)对应于处理程序数(函数指针数组中的项)。简单地针对 sizeof(func_pointer_array)/sizeof(*func_pointer_array).
  • 静态断言枚举中的最后一项(称为 STATES_N 或类似的东西)
  • 必须在 ROM 中分配函数指针数组,错误 detection/CRC。其他安全要求将指向此需求,最简单的解决方法是使用具有 "ECC flash" 的微控制器。在 ECC 出现之前的日子里,您必须改为计算整个 ROM 的 CRC,这既复杂又乏味,但也可以做到。您还可以创建 table 的相同 ROM 镜像以防止闪存损坏。
  • 唯一允许您的状态机代码调用的函数指针是从此安全函数指针数组中获得的。鉴于您的状态机调用看起来像 STATE_MACHINE[i]();,其中 STATE_MACHINE 是函数指针数组,那么只需添加 i 的 run-time 检查以确保它始终有效。
  • 对于程序中的其他函数指针,例如回调函数,确保它们被初始化为指向 ROM 中的有效函数。尽可能使用 const 指针(指针本身是 read-only)。如果您需要在运行时 re-assign 它们,请确保它们在调用它们之前指向有效的函数。

上述状态机是惯用的并且非常安全,可能比代码中其他地方的普通函数调用更安全。您当然必须确保以安全合理的方式完成状态转换,但这与函数指针无关。

避免递归

这主要是为了教育程序员不要使用它,函数指针或没有函数指针(似乎这可以防止丰田错误)。

既不难发现也不难避免递归,因此 half-decent 代码审查手续应该足以防止它。没有一个经验丰富的嵌入式系统程序员,无论 safety-critical 系统经验如何,都不会赞成包含递归的代码。

您 could/should 设置了一个 in-house 设计规则,规定所有 safety-related 代码必须由具有 n[=85= 的资深 C 程序员审查和批准]年safety-critical程序设计经验。

此外,您还应该使用静态分析器工具检查递归(即使它们无法通过函数指针检测递归)。如果您有一个符合任何版本 MISRA-C 的静态分析器,则包含在内。

关于无意的递归,可以通过上面提到的防御性编程方法来避免。

混淆函数指针语法

C 中的函数指针语法确实很混乱,看看

int (*(*func)[5])(void);

或其他一些荒谬的例子。它可以通过始终对函数指针类型强制执行 typedef 来解决。

(参考:Les Hatton,Safer C,p184 "From a safety-related viewpoint, the simple answer is that they should never be allowed outside the typedef mechanism.")

您可以通过两种不同的方式对它们进行类型定义,我更喜欢这个:

typedef int func_t (void);
func_t* fptr;

因为这不会将指针隐藏在 typedef 后面,这通常是不好的做法。但是,如果您觉得 table 更适合

typedef int (*func_t) (void);
func_t fptr;

那也可以练习。