embedded式C ++:使用STL还是不行?

我一直是一名embedded式软件工程师,但通常在OSI堆栈的第3层或第2层。 我不是一个硬件人。 我通常总是做电信产品,通常是手机/手机,通常意味着像ARM 7处理器。

现在,我发现自己处于一个更加通用的embedded式世界,在一个小型的初创阶段,我可能会转向“不是那么强大”的处理器(这是主观的一点) – 我无法预测哪一个。

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

现在我必须决定是否使用或放弃整个公司的STL(它将进入一些非常核心的S / W)。

我跳哪个方向? 超级安全和失去了什么构成C + +(imo,它不仅仅是语言定义),也许以后遇到问题,或者不得不添加大量的exception处理,也许现在一些其他的代码?

我很想试试Boost ,但是1)我不确定它是否会移植到我想要使用的每个embedded式处理器上,2)在他们的网站上,他们说他们不保证/推荐它的某些部分对于embedded式系统(特别是FSM,这似乎很奇怪)。 如果我去升压,我们后来发现一个问题….

超级安全和失去了什么构成C + +(imo,它不仅仅是语言定义),也许以后遇到问题,或者不得不添加大量的exception处理,也许现在一些其他的代码?

我们在游戏世界里也有类似的争论,双方都会下来。 关于引用的部分,为什么你会担心失去“构成C ++的大部分内容”? 如果不务实,就不要使用它。 不要紧,如果它是“C ++”或不。

运行一些testing。 你能以满足你的方式来解决STL的内存pipe理吗? 如果是这样,是值得的努力? STL和boost的很多问题都是为了解决问题而devise的,如果你devise的时候没有devise,以避免偶然的dynamic内存分配… STL解决你面临的具体问题吗?

很多人在紧张的环境中解决了STL,并对此感到满意。 很多人只是避免它。 有些人提出了全新的标准 。 我不认为有一个正确的答案。

我每天都在实时embedded式系统上工作。 当然,我对embedded式系统的定义可能和你的不同。 但是我们充分利用了STL和exception,并没有遇到任何难以pipe理的问题。 我们也使用dynamic内存(速度非常快;每秒分配大量数据包等),而且还不需要使用任何自定义的分配器或内存池。 我们甚至在中断处理程序中使用了C ++。 我们不使用提振,而只是因为某个政府机构不会让我们。

根据我们的经验,只要您使用自己的头脑并进行自己的基准testing,就可以在embedded式环境中使用许多现代C ++特性。 我强烈build议您使用Scott Meyer的Effective C ++ 3rd版以及Sutter和Alexandrescu的C ++编码标准,以帮助您使用C ++,并且具有明智的编程风格。

编辑:在这两年后得到upvote,让我发布更新。 在我们的开发中,我们走得更远,我们终于在高性能条件下的标准库容器太慢的代码中碰到了一些问题。 在这里,我们确实采用了自定义algorithm,内存池和简化的容器。 这就是C ++的美妙之处,您可以使用标准库,并获得它为90%的用例提供的所有优点。 当遇到问题时,不要把它全部丢掉,只要手动优化故障点即可。

其他职位已经解决了dynamic内存分配,exception和可能的代码膨胀的重要问题。 我只想补充一点:不要忘记<algorithm> ! 无论您使用STL向量还是简单的C数组和指针,仍然可以使用sort()binary_search()random_shuffle() ,用于构build和pipe理堆的函数等等。这些例程几乎肯定会更快,比你自己build立的版本。

例如:除非您仔细考虑,否则您自己构build的随机algorithm可能会产生偏态分布 ; random_shuffle()不会。

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

  1. STL分配器很慢,臃肿,效率低下
  2. 编译器实际上并不擅长内联所有这些深层次的函数调用
  3. STL分配器不支持显式alignment
  4. GCC和MSVC的STL提供的STLalgorithm不是非常高效的,因为它们非常不依赖于平台,因此错过了很多可以产生巨大差异的微观优化。

几年前,我们公司决定不使用STL,而是实现我们自己的最高性能,更容易debugging和更保守的容器系统。 这是很多工作,但它已经多次偿还了自己。 但是,我们的产品是一个空间,在这个空间内,CPU和内存大小可以在16.6ms内挤入多less。

至于例外情况:游戏机的游戏速度慢 ,任何告诉你的人都没有尝试过。 简单地编译启用它们会减慢整个程序,因为必要的序言/结语代码 – 如果你不相信我自己,就自己测量。 有序的CPU比在x86上更糟糕。 出于这个原因,我们使用的编译器甚至不支持C ++exception。

性能提升并不是避免了exception抛出的成本,而是完全禁用exception。

让我开始说,我从来没有做过embedded式工作几年,从来没有在C + +,所以我的build议是值得你付出的每一分钱…

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

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

我认为所有的容器类都会让你定义自己的分配函数,所以如果你想从一个池中分配,你可以做到这一点。

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

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

  3. 所以,我认为你可以在embedded式系统中使用STL 🙂

它基本上取决于你的编译器和内存的数量。 如果你有超过几个RAM的内存,dynamic内存分配有很大的帮助。 如果从你拥有的标准库实现malloc没有被调整到你自己的内存大小,你可以自己写,或者在Ralph Hempel的mm_malloc中有很好的例子,你可以用它来写你的新的和删除的操作符。

我不同意那些重复了那些exception和stl容器太慢或太臃肿等等的模式。当然,它比一个简单的C的malloc增加了一些代码,但是明智地使用exception可以使代码更加清晰,避免在C中错误检查太多

我们必须记住,STL分配器会增加两个分配的分配,这意味着有时它会做一些重新分配,直到它达到正确的大小,你可以预防,所以它变得像所需的malloc一样便宜大小,如果你知道大小分配无论如何。

例如,如果在vector中有一个很大的缓冲区,那么在某个时候,它可能会重新分配,并且在重新分配和移动数据的同时,以1.5倍于您打算使用的内存大小结束。 (例如,在某个时候,它分配了N个字节,通过append或插入迭代器添加数据,并分配2N个字节,复制第一个N并释放N.您在某个点上分配了3N个字节)。

所以最后它有很多好处,而且如果你知道你在做什么,那就付出代价。 您应该了解一些C ++如何在embedded式项目中使用它,而不会感到惊讶。

对于固定缓冲区的人员和重新设置,您可以随时在新操作员的内部进行重置,或者如果您内存不足,但这意味着您做了一个糟糕的devise,可能会耗尽您的内存。

ARM realview 3.1引发的exception:

 --- 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> 

看起来并不那么可怕,如果不抛出exception,块或函数内部不会添加开销。

除了所有的评论之外,我还会build议你阅读C ++性能技术报告,它专门讨论你感兴趣的主题:在embedded式(包括硬实时系统)中使用C ++; 通常如何实现exception处理以及它具有哪些开销; 免费店铺分配的开销。

这个报告真的很棒,正如许多关于C ++性能的stream行尾巴一样。

开源项目“Embedded Template Library(ETL)”通过提供/实现一个库来针对embedded式应用程序中使用的STL的常见问题:

  • 确定性行为
  • “在编译时创build一个容器大小或最大大小的容器,这些容器应该与STL中提供的容器大致相同,并且具有兼容的API。”
  • 没有dynamic内存分配
  • 不需要RTTI
  • 很less使用虚拟function(只有当绝对必要时)
  • 一套固定容量的容器
  • 将容器caching友好的存储为连续分配的内存块
  • 缩小的容器代码大小
  • types安全的智能枚举
  • CRC计算
  • 校验和和散列函数
  • 变种=种类安全的联盟
  • 断言,exception,error handling程序的select或不检查错误
  • 严重的单位testing
  • 有据可查的源代码
  • 和其他function… … –

您也可以考虑由ESR实验室提供的embedded式开发人员的商业C ++ STL 。

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

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

至于例外问题,我不会去那里。 例外是严重的代码放缓 ,因为它们会导致每个块( { } )在前后都有代码,从而允许捕获exception并销毁其中包含的任何对象。 我手头上没有硬数据,但是每次看到这个问题出现时,我都看到使用exception导致大规模放缓的绝大部分证据。

编辑:
由于很多人写评论说exception处理慢,所以我想我会添加这个小小的注释(感谢在评论中写这个的人,我认为在这里添加它会很好)。

exception处理减慢你的代码的原因是因为编译器必须确保每个块( {} ),从一个exception抛出到它处理的地方,必须释放其中的任何对象。 这是添加到每个块的代码,无论是否有人抛出exception(因为编译器不能在编译时判断这个块是否是exception“链”的一部分)。

当然,这可能是一个旧的方式,在新的编译器中已经快得多了(我没有完全掌握C ++编译器的优化)。 知道最好的方法就是运行一些示例代码,打开和closuresexception(包括几个嵌套函数),并计算时间差异。

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