在实验室中起作用的东西并不总是在现场起作用,这通常是由于意外的交互和未发现的错误。防御性编程有助于使设计更具弹性,但开发能够处理不可预见情况的嵌入式软件并非易事,这需要纪律和深谋远虑。这里有五个技巧可以帮助嵌入式开发人员成为更好的防御性程序员。
技巧1——校验和或 CRC 数据
使用校验和和循环冗余校验 (CRC) 算法是开发人员验证通过串行链路发送的数据确实是否正确的一种好方法。经过审查的嵌入式系统在测试平台的受控环境中将始终按预期运行。然而,一旦系统被释放到野外,系统运行的环境就变得非常未知。嘈杂的环境可能会产生通信噪声,从而导致位翻转和误读数据。检测这些损坏数据的最大希望是通过使用校验和或 CRC 对数据进行完整性检查。
技巧2——契约式设计
契约式设计是一种开发软件的方法,它产生高度定义的软件接口,其中每个功能都与明确定义的前置条件和后置条件相关联。这个想法是,如果应用程序要调用特定函数,调用的应用程序必须满足函数的先决条件才能获得有效的响应或操作。契约式设计对于开发人员来说是一个强大的工具,因为它明确地指定了函数期望接收的内容以及在有效前提条件下保证的输出将是什么。由于期望不是“从字里行间读取”,调用该函数的嵌入式开发人员确切地知道为了使用该函数而期望是什么。
技巧3——使用断言
断言宏是开发人员在应用程序中的给定时刻验证他们对应用程序的假设的好方法。断言的使用对于在错误发生的那一刻捕捉程序中的错误和意外行为非常有效。断言甚至可以在按契约式设计的环境中使用,以验证契约的前置条件和后置条件是否得到满足。
技巧4——检查指针和缓冲区
指针和缓冲区是开发人员似乎总是让自己陷入麻烦的两个地方。在用C开发嵌入式系统时,很容易意外地取消对空指针的引用或溢出缓冲区。防御性的程序员应该在取消引用一个指针之前检查它的有效性。指针是否为空?不要取消引用它!指针中存储的值是有效值吗?如果是,则取消引用。
指针算法和数组的使用也非常危险。嵌入式开发人员应该在缓冲区和指针算术运算中添加边界检查,以确保结果保持在它们应该在的内存空间内。意外地覆盖一个字节的内存可能会给嵌入式系统带来灾难性的后果,更重要的是,会给用户带来灾难性的后果。
技巧5——使用堆栈监视器
执行最坏情况下的堆栈分析并正确确定堆栈大小是一项艰巨的任务。通常堆栈的大小要么保留编译器的默认设置,要么开发人员在一张纸上草草写下一些可能的值,并使用“eeny meeny miny moe”技术。这两种技术都不充分,最坏的情况是堆栈溢出。
开发人员可以通过监视此类事件来帮助防止堆栈溢出。大多数实时操作系统都内置了堆栈监视器。启用堆栈监视器只不过是用RTOS的配置来调整宏。在裸机系统中,开发人员需要更加积极主动,要么自己编写一个堆栈监视器,要么使用互联网上提供的许多堆栈监视器中的一个。
最后的想法
这五个技巧只是嵌入式开发人员如何通过防御性编程来改进嵌入式软件的几个例子。还有许多其他技术,如编写安全代码和加密数据,可以帮助提高嵌入式系统在不可预见的情况下继续运行的机会。