首先,NASA 的文件不是正典。首先询问哪些法律/指令/标准/要求/文件强制您遵循 NASA 文件。如果它没有在任何地方强制执行(这似乎很有可能,即使在 NASA 本身也是如此),你没有义务遵循它,你可以忽略整个事情。
如果没有将废话视为废话,您可以在碰壁时使用通常的程序以达到安全标准:解决方案始终是详细记录规定的规则如何没有意义,然后用耳光打他们的脸他们自己的方法。
因此,与其放弃函数指针,不如通过下面描述的方法确保以安全的方式使用它们。
由于所有与安全相关的设计都归结为风险评估,因此您将始终拥有:
错误 -> 原因 -> 危险 -> 安全措施
根据 NASA 文档中给定的(糟糕的)基本原理,您可以通过以下方式证明“避免函数指针”的安全措施是合理的:
执行了错误的代码 -> 损坏的函数指针 -> 失控代码/非法操作码
堆栈溢出 -> 函数指针递归 -> 内存损坏
困惑的程序员 -> 函数指针语法 -> 意外的程序功能
这是一个相当模糊且值得怀疑的风险评估,但这就是 NASA 文件归结为的内容。
我建议使用以下安全措施,而不是针对上述 3 种危险“避免函数指针”:
- 防御性编程和断言。
- 防御性编程和断言。教育程序员。
- 使用 typedef。教育程序员。
防御性编程和断言
- 对于状态机,确保状态数(枚举中的项)与处理程序数(函数指针数组中的项)相对应。只需针对
sizeof(func_pointer_array)/sizeof(*func_pointer_array) 静态断言枚举中的最后一项(称为STATES_N 或类似名称)。
- 函数指针数组必须在带有错误检测/CRC 的 ROM 中分配。其他安全要求将指向此需求,最简单的解决方法是使用带有“ECC 闪存”的微控制器。回到 ECC 之前的日子,您必须改为在整个 ROM 上计算 CRC,这既复杂又乏味,但也可以完成。您还可以创建表的相同 ROM 镜像,以防止闪存损坏。
- 唯一允许您的状态机代码调用的函数指针是从这个安全函数指针数组中获得的。鉴于您的状态机调用看起来像
STATE_MACHINE[i]();,其中STATE_MACHINE 是函数指针数组,那么只需添加i 的运行时检查以确保它始终有效。
- 对于程序中的其他函数指针,例如回调函数,请确保将它们初始化为指向 ROM 中的有效函数。尽可能使用
const 指针(指针本身是只读的)。如果您需要在运行时重新分配它们,请在调用它们之前确保它们指向一个有效的函数。
上述类型的状态机是惯用的并且非常安全,可能比代码中其他地方的普通函数调用安全得多。当然,您必须确保以安全合理的方式完成状态转换,但这与函数指针无关。
避免递归
这主要是关于教育程序员不要使用它,函数指针或没有函数指针(似乎这可以防止丰田错误)。
递归既不难发现也不难避免,所以半体面的代码审查形式应该足以防止它。没有经验丰富的嵌入式系统程序员,无论对安全至关重要的系统经验如何,都不会认可包含递归的代码。
您可以/应该制定内部设计规则,规定所有与安全相关的代码必须由具有 n 年安全关键程序设计经验的资深 C 程序员审查和批准。
此外,您还应该使用静态分析器工具检查递归(即使它们无法通过函数指针检测递归)。如果您有符合任何 MISRA-C 版本的静态分析器,则包括在内。
关于无意识的递归,上面提到的防御性编程方法可以避免。
Confusig 函数指针语法
C 中的函数指针语法确实很混乱,看看
int (*(*func)[5])(void);
或其他一些荒谬的例子。它可以通过始终对函数指针类型强制执行 typedef 来解决。
(参考:Les Hatton,Safer C,p184“从与安全相关的角度来看,简单的答案是它们永远不应该被允许在 typedef 机制之外。”)
有两种不同的方式可以对它们进行 typedef,我更喜欢这个:
typedef int func_t (void);
func_t* fptr;
因为这不会将指针隐藏在 typedef 后面,这通常是不好的做法。但是,如果您对替代方案感到更自在
typedef int (*func_t) (void);
func_t fptr;
那也可以练习。