嵌入式开发:编写内存安全C++的3个技巧
内存安全编程语言可以帮助解决特定的问题,并降低内存相关错误的可能性,但它们不是包罗万象的。在一种内存安全的语言中,你仍然可能会遇到内存问题和错误。所以内存安全语言是嵌入式开发人员可以用来解决问题的另一个工具。
虽然人们普遍对迁移到Rust这样的语言感到兴奋和感兴趣,但在嵌入式领域,仍然存在许多挑战,例如:
l培训工程师并让他们快速掌握一门新语言所需的时间和成本
l如何处理遗留代码以及迁移现有代码的时间和成本
l现有供应商工具链中的语言支持
如果开发人员注意他们正在做的事情并遵循行业最佳实践和过程,内存安全语言帮助开发人员避免的许多错误也可以通过使用C/C++来避免。让我们研究五种可以帮助开发人员提高内存安全性的C++技术。
技巧1——智能指针
内存问题的一个重要原因是原始指针的使用。嵌入式开发人员都熟悉指针的使用。你可以使用它们来指向外围存储器和寄存器,高效地将数据传递给函数,等等。C和早期版本的C++中的原始指针的问题是开发人员可以随心所欲地做他们想做的事情。在许多情况下,会出现内存泄漏、访问范围外的变量等问题。
在现代C++中,开发人员可以利用智能指针,包括:
lauto_ptr(在C++11中不推荐使用)
lunique_ptr
lshared_ptr
lweak_ptr
我们可以通过讨论这项工作如何超出我们的范围来讨论一般概念。
你通常可以将智能指针视为包装在类中的原始指针。智能指针有几个用途。首先,unique_ptr可用于确保当指针超出范围时,发生的任何内存分配也被释放。使用原始指针并不能保证这种可能导致内存泄漏的行为。接下来,智能指针还要有主人翁意识。例如,unique_ptr防止复制其包含的指针。只允许基础指针的一个所有者。如果另一个对象想要使用unique_ptr指向的资源,可以使用move语义来转移所有权。
使用C++的嵌入式开发人员应该避免像在C中那样使用原始指针,相反,智能指针是一种更好的做法,有助于防止导致错误和安全漏洞的常见内存问题。
技巧2——避免使用动态内存和堆
几十年来,避免动态内存和堆一直是基于微控制器的嵌入式系统的标准最佳实践。当然,使用它们是可能的,我有时这样做是为了证明一个观点,但是也有可能引入与内存相关的错误,比如内存泄漏、碎片和其他潜在的问题。
使用C++的嵌入式开发人员可以通过避免动态内存和堆来提高避免内存问题的机会。有几种不同的方法可以做到这一点。首先,开发者应该避免在嵌入式系统中使用标准模板库(STL)。一般来说,STL使用了大量的动态内存分配。接下来,开发人员可以禁用运行时类型信息(RTTI)。最后,禁用异常是另一种选择。异常的内存是以未指定的方式分配的,在大多数实现中是使用堆。
避免内存分配并使用堆可以立即消除内存泄漏、内存不足错误、堆碎片等问题。
技术3–RAII
很容易概括地说,嵌入式开发人员不应该使用动态内存或堆。不过,最佳实践是一种概括,有时你可能会发现自己处于必须这样做的情况。例如,当必须使用动态内存和堆时,开发人员可以通过遵循资源获取初始化(RAII)技术来避免内存问题。
RAII是一种技术,嵌入式开发人员将对象的生命周期与其自己的资源联系起来。这个想法很简单。如果应用程序获取一个资源,一个对象应该与该资源相关联,该资源调用一个初始化该资源的构造函数。该对象在其生存期内拥有该资源。当对象超出范围时,析构函数被执行,资源和所有分配的内存被释放。
要考虑的其他技术
首先,熟悉一下零规则、五规则和三规则。当设计一个类时,这些规则有助于决定何时应该创建用户定义的复制构造函数、复制赋值操作符、析构函数、移动构造函数和移动操作符。
其次,使用静态分析工具来查找内存泄漏、线程问题、bug和其他潜在问题。同样,许多静态分析工具已经存在,比如clang、cppcheck、valgrind和商业工具。
接下来,熟悉现代C++。C++语言中有很多特性,但是实际上一个嵌入式开发者只需要其中的一小部分。你可以识别出符合你需求的子集,并抛弃其他所有东西。我知道我做过,我也用c做了同样的事情。我们的目标不是使用每种语言的特性,而是使用语言中的工具来帮助你编写健壮的实时嵌入式软件。
最后,关注对C++标准所做的更改。C++每三年更新一次。语言在不断发展,新的工具也在不断增加。安全性和内存安全是至关重要的,我们很可能会在未来看到语言在这方面的许多改进。
结论
C++不是内存安全的语言;然而,许多特性和技术可以用来编写内存安全的代码。最终,无论你是否使用内存安全语言,嵌入式开发人员都需要考虑他们正在做什么,以及它如何影响内存。