嵌入式C ++:是否使用STL?

时间:2010-02-09 01:54:37

标签: c++ stl embedded

我一直是嵌入式软件工程师,但通常在OSI堆栈的第3层或第2层。我不是一个硬件家伙。我一般都经常使用电信产品,通常是手机/手机,这通常意味着像ARM 7处理器一样。

现在我发现自己处于一个更通用的嵌入式世界中,在一个小型初创公司中,我可能会转向“不那么强大”的处理器(这是主观位) - 我无法预测哪个。

我已经阅读了很多关于在嵌入式系统中使用C ++在C ++中的争论,并且没有明确的答案。有一些关于可移植性的小担忧,还有一些关于代码大小或运行时间的担忧,但我有两个主要问题:
  1 - 异常处理;我仍然不确定是否使用它(见Embedded C++ : to use exceptions or not?
  2 - 我非常不喜欢嵌入式系统中的动态内存分配,因为它可能会引入问题。我通常有一个缓冲池,它在编译时静态分配,只提供固定大小的缓冲区(如果没有缓冲区,系统复位)。当然,STL会进行大量的动态分配。

现在我必须决定是否使用或放弃STL - 对于整个公司,永远(它将进入一些非常核心的s / w)。

我跳哪个方向?超级安全&失去了很多构成C ++的东西(imo,它不仅仅是语言定义),可能会在以后遇到问题或者必须添加大量的异常处理和现在可能还有其他一些代码吗?

我很想和Boost一起去,但是1)我不确定它是否会移植到我可能想要使用的每个嵌入式处理器上2)在他们的网站上,他们说他们没有保证/推荐嵌入式系统的某些部分(特别是FSM,这看起来很奇怪)。如果我选择Boost&我们后来发现了一个问题......

11 个答案:

答案 0 :(得分:42)

我每天都在使用实时嵌入式系统。当然,我对嵌入式系统的定义可能与您的不同。但是我们充分利用STL和异常,并且不会遇到任何无法解决的问题。我们还使用动态内存(以非常高的速率;每秒分配大量数据包等)并且还不需要求助于任何自定义分配器或内存池。我们甚至在中断处理程序中使用了C ++。我们不使用提升,只是因为某个政府机构不会让我们这么做。

根据我们的经验,只要您使用头脑并执行自己的基准测试,就可以在嵌入式环境中使用许多现代C ++功能。我强烈建议您使用Scott Meyer的 Effective C ++ 第3版以及Sutter和Alexandrescu的 C ++编码标准来帮助您使用具有理智编程风格的C ++。

编辑:2年后获得upvote后,让我发布更新。我们在开发过程中走得更远,最终在我们的代码中遇到了标准库容器在高性能条件下速度太慢的问题。实际上,我们在这里采用了自定义算法,内存池和简化容器。这就是C ++的美妙之处,你可以使用标准库并获得它为90%的用例提供的所有好东西。当你遇到问题时,你不会全力以赴,只需手动优化故障点。

答案 1 :(得分:32)

  

超级安全&失去了很多东西   构成C ++(imo,它不仅仅是   只是语言定义)和   可能会遇到问题或者有问题   添加大量的异常处理和   现在可能还有其他一些代码吗?

我们在游戏世界中也有过类似的争论,人们对双方都有所了解。关于引用的部分,为什么你会担心失去“C ++的大部分内容”?如果它不实用,请不要使用它。它是否是“C ++”并不重要。

运行一些测试。您能以满足您需求的方式绕过STL的内存管理吗?如果是这样,是否值得努力?很多问题STL和boost的设计只是简单解决如果你的设计是为了避免随意的动态内存分配... STL是否解决了你面临的特定问题?

许多人在紧张的环境中处理STL并对此感到满意。很多人都避免它。有些人提议entirely new standards。我认为没有一个正确的答案。

答案 2 :(得分:24)

其他帖子已经解决了动态内存分配,异常和可能的代码膨胀等重要问题。我只想补充一点:不要忘记<algorithm>!无论您使用STL向量还是纯C数组和指针,您仍然可以使用sort()binary_search()random_shuffle(),构建和管理堆的功能等。这些例程几乎可以肯定比你自己构建的版本更快,更少bug。

示例:除非您仔细考虑,否则您自己构建的随机算法is likely to produce skewed distributions; random_shuffle()不会。

答案 3 :(得分:19)

Electronic Arts写了a lengthy treatise关于为什么STL不适合嵌入式控制台开发以及为什么他们必须自己编写。这是一篇详细的文章,但最重要的原因是:

  1. STL分配器缓慢,臃肿,
  2. 编译器实际上并不擅长内联所有深层函数调用
  3. STL分配器不支持显式对齐
  4. GCC和MSVC的STL附带的STL算法效率不高,因为它们与平台无关,因此错过了许多可以产生重大影响的微观优化。
  5. 几年前,我们公司决定不使用STL,而是实现我们自己的容器系统,这些容器具有最高性能,更易于调试和更保守的内存。这是很多工作,但它已经多次偿还。但是我们的产品是一个空间,在这个空间中,产品可以根据给定的CPU和内存大小在16.6ms内进行竞争。

    关于控制台上的例外:they are slow,任何告诉你的人都没有尝试计时。简单地使用它们进行编译会因为必要的prolog / epilog代码而减慢整个程序的速度 - 如果你不相信我自己测量它。在有序CPU上比在x86上更糟糕。因此,我们使用的编译器甚至不支持C ++异常。

    性能提升不是避免异常抛出的成本 - 而是完全禁用异常。

答案 4 :(得分:14)

首先让我说我几年没有完成嵌入式工作,而且从来没有用过C ++,所以我的建议值得为你付出的每一分钱......

STL使用的模板永远不会生成您自己不需要生成的代码,所以我不担心代码膨胀。

STL不会自行抛出异常,所以这不应该是一个问题。如果你的课不扔,你应该是安全的。将对象初始化分为两部分,让构造函数创建一个裸骨对象,然后在返回错误代码的成员函数中执行任何可能失败的初始化。

我认为所有的容器类都允许你定义自己的分配函数,所以如果你想从池中分配你可以实现它。

答案 5 :(得分:5)

  1. 对于内存管理,您可以实现自己的分配器,它从池中请求内存。并且所有STL容器都有分配器的模板。

  2. 对于异常,STL不会抛出很多异常,一般来说,最常见的是:内存不足,在你的情况下,系统应该重置,所以你可以在分配器中进行重置。其他如超出范围,您可以避免使用它。

  3. 所以,我认为你可以在嵌入式系统中使用STL:)

答案 6 :(得分:4)

开源项目"Embedded Template Library (ETL)"通过提供/实现库来解决嵌入式应用程序中使用的STL的常见问题:

  • 确定性行为
  • “创建一组容器,其中大小或最大大小是在编译时确定的。这些容器应该与STL中提供的容器大致相同,并且具有兼容的API。”
  • 没有动态内存分配
  • 不需要RTTI
  • 很少使用虚拟功能(仅在绝对必要时使用)
  • 一组固定容量容器
  • 将容器缓存友好存储为连续分配的内存块
  • 减少了容器代码大小
  • typesafe smart enumerations
  • CRC计算
  • 校验和&amp;哈希函数
  • variants =一种类型安全的工会
  • 选择断言,异常,错误处理程序或不对错误进行检查
  • 经过严格的单元测试
  • 详细记录的源代码
  • 和其他功能......

您还可以考虑由E.S.R提供的广告C++ STL for Embedded Developers。实验室。

答案 7 :(得分:3)

它主要取决于你的编译器和你拥有的内存量。如果你有超过几公斤的RAM,动态内存分配有很大帮助。如果你所拥有的标准库中malloc的实现没有调整到你的内存大小,你可以自己编写,或者有一些很好的例子,例如mm_malloc from Ralph Hempel可以用来编写你的新操作符和删除操作符顶部。

我不同意那些重复异常和stl容器太慢或过于膨胀的模式等等。当然,它比简单的C的malloc添加了更多的代码,但明智地使用异常可以使代码非常清楚,避免过多的错误检查C中的模糊。

需要记住的是,STL分配器将以2的幂增加其分配,这意味着有时它将进行一些重新分配,直到达到正确的大小,您可以通过保留来阻止如果你知道要分配的大小,它就像一个所需大小的malloc一样便宜。

例如,如果向量中有一个大缓冲区,那么在某些时候它可能会进行重新分配,并且在重新分配和移动数据时,在某个时刻使用的内存大小会增加1.5倍。 (例如,在某些时候它分配了N个字节,你通过append或插入迭代器添加数据,它分配2N个字节,复制第一个N并释放N.你在某个时刻分配了3N个字节)。

所以最后它有很多优点,如果你知道自己在做什么就付出代价。您应该了解C ++如何在嵌入式项目中使用它而不会出现意外。

对于固定缓冲区和复位的人来说,你可以随时在新操作符内部重置,或者如果内存不足则重置,但这意味着你做了一个糟糕的设计,可能耗尽你的记忆。

使用ARM realview 3.1抛出异常:

--- OSD\#1504 throw fapi_error("OSDHANDLER_BitBlitFill",res);
   S:218E72F0 E1A00000  MOV      r0,r0
   S:218E72F4 E58D0004  STR      r0,[sp,#4]
   S:218E72F8 E1A02000  MOV      r2,r0
   S:218E72FC E24F109C  ADR      r1,{pc}-0x94 ; 0x218e7268
   S:218E7300 E28D0010  ADD      r0,sp,#0x10
   S:218E7304 FA0621E3  BLX      _ZNSsC1EPKcRKSaIcE       <0x21a6fa98>
   S:218E7308 E1A0B000  MOV      r11,r0
   S:218E730C E1A0200A  MOV      r2,r10
   S:218E7310 E1A01000  MOV      r1,r0
   S:218E7314 E28D0014  ADD      r0,sp,#0x14
   S:218E7318 EB05C35F  BL       fapi_error::fapi_error   <0x21a5809c>
   S:218E731C E3A00008  MOV      r0,#8
   S:218E7320 FA056C58  BLX      __cxa_allocate_exception <0x21a42488>
   S:218E7324 E58D0008  STR      r0,[sp,#8]
   S:218E7328 E28D1014  ADD      r1,sp,#0x14
   S:218E732C EB05C340  BL       _ZN10fapi_errorC1ERKS_   <0x21a58034>
   S:218E7330 E58D0008  STR      r0,[sp,#8]
   S:218E7334 E28D0014  ADD      r0,sp,#0x14
   S:218E7338 EB05C36E  BL       _ZN10fapi_errorD1Ev      <0x21a580f8>
   S:218E733C E51F2F98  LDR      r2,0x218e63ac            <OSD\#1126>
   S:218E7340 E51F1F98  LDR      r1,0x218e63b0            <OSD\#1126>
   S:218E7344 E59D0008  LDR      r0,[sp,#8]
   S:218E7348 FB056D05  BLX      __cxa_throw              <0x21a42766>

看起来并不那么可怕,如果没有抛出异常,{}块或函数内不会增加任何开销。

答案 8 :(得分:3)

除了所有评论之外,我建议你阅读Technical Report on C++ Performance,它专门针对您感兴趣的主题:在嵌入式中使用C ++(包括硬实时系统);如何实现异常处理以及它具有哪些开销;免费商店分配的开销。

该报告非常好,因为很多关于C ++性能的流行尾巴已经破坏了。

答案 9 :(得分:1)

嵌入式系统中STL的最大问题是内存分配问题(正如你所说,这会导致很多问题)。

我认真研究创建自己的内存管理,通过覆盖新的/删除操作符来构建。我很确定有一点时间,它可以完成,而且几乎肯定是值得的。

至于例外问题,我不会去那里。例外是代码的严重减速,因为它们导致每个块({ })之前和之后都有代码,允许捕获异常并破坏其中包含的任何对象。我手边没有关于这方面的硬数据,但每次我看到这个问题出现时,我都看到了压倒性的证据,表明使用异常造成了大幅放缓。

修改
由于很多人都写了评论说明异常处理慢,我想我会添加这个小小的说明(感谢那些在评论中写这篇文章的人,我认为这样做会很好在这里添加)。

异常处理减慢代码的原因是因为编译器必须确保每个块({})从异常处所抛出到它所处理的位置,必须释放其中的任何对象。这是添加到每个块的代码,无论是否有人抛出异常(因为编译器无法在编译时告诉该块是否将成为异常“链”的一部分)。

当然,这可能是一种老式的做事方式,在新的编译器中速度要快得多(我对C ++编译器的优化并不是最新的)。知道的最好方法就是运行一些示例代码,打开和关闭异常(包括几个嵌套函数),然后计算差异。

答案 10 :(得分:1)

在我们的嵌入式扫描仪项目中,我们正在开发一个带有ARM7 CPU的电路板,STL没有带来任何问题。当然,项目细节很重要,因为动态内存分配可能不是当今许多可用电路板和项目类型的问题。