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


74

我一直是嵌入式软件工程师,但通常在OSI堆栈的第3层或第2层。我不是一个真正的硬件专家。我通常总是做电信产品,通常是手提电话/手机,这通常意味着类似ARM 7处理器的东西。

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

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

现在,我必须决定是整个公司永远使用还是放弃STL(它涉及一些非常核心的软件)。

我跳哪条路?超级安全并丢失了构成C ++的大部分内容(imo,不仅是语言定义),而且以后可能会遇到问题,还是不得不添加大量异常处理以及现在可能还有其他一些代码?

我很想只使用Boost,但是1)我不确定它是否可以移植到我可能要使用的每个嵌入式处理器上,以及2)在他们的网站上,他们说他们不保证/不推荐它的某些部分。用于嵌入式系统(尤其是FSM,这似乎很奇怪)。如果我去助推器&以后我们发现问题....


8
STL是C ++语言的一部分,如果您担心内存,请用自己的内存管理器替换new运算符并删除它。
GManNickG 2010年

6
您检查过美国标准语言(STL)吗?ustl.sourceforge.net
Manuel

1
看起来不错,谢谢。当然,我需要更深入地研究可移植性。
Mawg说恢复Monica

3
大多数C ++容器都带有一个“分配器”对象,该对象告诉它在哪里获取动态内存。您可以轻松地完全控制内存。(并非所有事情都需要分配器,但是大多数事情都需要这样做)
Mooing Duck 2012年

1
看看Meyer关于使用C ++进行嵌入式的幻灯片:htrd.su/wiki/_media/zhurnal/2013/03/28/…–
Claudio

Answers:


35

超级安全并丢失了构成C ++的大部分内容(imo,不仅是语言定义),而且以后可能会遇到问题,还是不得不添加大量异常处理以及现在可能还有其他一些代码?

在游戏世界中,我们也有类似的辩论,双方都倒下了。关于引用的部分,为什么您会担心丢失“组成C ++的大部分内容”?如果不实用,请不要使用它。是否为“ C ++”无关紧要。

运行一些测试。您能否以满意的方式解决STL的内存管理问题?如果是这样,值得付出努力吗?如果您设计为避免偶然的动态内存分配,那么STL和boost的很多问题只是为了解决而已。STL是否可以解决您面临的特定问题?

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


1
谢谢,丹,这个和其他人(也投了赞成票)使我真正想到了。由于我们拥有嵌入式系统,因此我们拥有自己的内存池。STL对我们大多数容器类有用。但是我们在初始化时将它们最大化。因此,要么我们忍受了该规则并且系统启动后不分配STL,要么我们可以使用普通的旧数组(指向静态分配对象的指针)
Mawg说,请恢复Monica 2010年

47

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

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

编辑:在这2年后获得支持后,让我发布更新。我们的开发工作还有很长的路要走,我们最终在代码中遇到了一些问题,在这些情况下,标准库容器在高性能条件下过慢。实际上,我们在这里使用了自定义算法,内存池和简化的容器。这就是C ++的优点,您可以使用标准库并获取90%用例中提供的所有优点。遇到问题时,您不会全力以赴,而只是手动优化问题点。


3
+1,有用的答案。但是我认为您对异常或代码膨胀的了解不如您想象的要多-请参阅我的评论以回应您对他人帖子的评论。
j_random_hacker 2010年

1
我的回答中确切出现在哪里“代码膨胀”?我感谢+1,但请直接将您的评论定向到该特定答案。
Brian Neal 2010年

听起来不错(是的,这两本书(和完整的Meyers“有效...”)现在都坐在我的显示器旁边。您瞄准的是哪种处理器?
Mawg说,请恢复Monica 2010年

您被允许使用Boost吗?
SS安妮

25

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

例子:除非您仔细考虑,否则您自己构建的混洗算法可能会产生偏态分布random_shuffle()惯于。


19

Electronic Arts的Paul Pedriana在2007年写了一篇冗长的论文,内容涉及STL为什么不适合嵌入式控制台开发,以及为什么他们必须自己编写。这是一篇详细的文章,但是最重要的原因是:

  1. STL分配器速度慢,肿且效率低下
  2. 编译器实际上并不擅长内联所有这些深层函数调用
  3. STL分配器不支持显式对齐
  4. GCC和MSVC的STL附带的STL算法性能不高,因为它们与平台无关,因此错过了很多可能导致很大差异的微优化。

几年前,我们公司决定完全不使用STL,而是实施我们自己的容器系统,该系统具有最高性能,易于调试且对内存更为保守。这是一项艰巨的工作,但它已经偿还了很多倍。但是,在我们的空间中,在给定的CPU和内存大小的情况下,产品可以争夺进入16.6ms的时间。

关于例外:在控制台上它们运行缓慢,否则任何告诉您的人都没有尝试对其进行计时。只需启用它们进行编译,由于必要的序言/后记代码,会使整个程序变慢–如果您不相信我,请自己进行测量。在有序CPU上,它甚至比x86上更糟。因此,我们使用的编译器甚至不支持C ++异常。

避免抛出异常的代价并没有带来太多的性能提升,而是完全禁用了异常。


12
您已链接2006年以来的一篇文章,但该文章已过时。在体面的现代编译器上,C ++异常并不慢。如果您要处理的嵌入式系统尚不具备像样的现代copmiler,那么您会遇到问题-但是笼统地说“关于例外:它们很慢”是完全错误的。
JoeG 2010年

15
像Herb Sutter和Andrei Alexandrescu这样的公认C ++专家不同意您的“例外很慢”的说法。如果您不使用异常,那么您现在将自己负责编写和检查错误返回代码,并且与现代编译器发出的异常代码相比,该代码几乎总是效率较低。此外,人们为检查错误代码而编写的代码(如果他们根本不愿意写的话)通常到处都是错误和错误。
Brian Neal 2010年

4
异常不是很慢,但是即使没有抛出异常,它们也会对至少一个流行的现代编译器(MSVC ++ 9)造成非零的运行时开销。要查看此内容,请尝试使用/ Fa编译(不链接)pastebin.com/m1fb29a45/EHa然后使用编译/EHsc,使用/ Fa生成程序集列表。在这两种情况下,都引入了Win32结构化异常处理(SEH)管理-这是将数据额外压入堆栈和设置FS段寄存器的方法。
j_random_hacker 2010年

10
这篇文章来自2006年,但我自己的时间是2009年8月。我已经阅读了所有有关异常不再缓慢的理论,但它们与我的实际测量结果不符
Crashworks 2010年

2
Brian:这些是EA的观点,不是我的观点,但#4是凭经验确定的。基本上,他们编写了自己的容器实现,并发现它们的运行速度比STL快得多。因此,STL不是最大效率。
Crashworks 2010年

15

首先,我要说我几年没有做过嵌入式工作,并且从来没有使用过C ++,所以我的建议值得您为此付出的每一分钱...

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

STL不会自行抛出异常,因此不必担心。如果您的课程没有抛出,那么您应该是安全的。将您的对象初始化分为两部分,让构造函数创建一个简单的骨骼对象,然后执行任何可能在返回错误代码的成员函数中失败的初始化。

我认为所有容器类都可以让您定义自己的分配函数,因此,如果要从池中分配,可以实现。


1
+1,我认为这是将构造工作从构造函数中移出的好几次机会之一。
j_random_hacker 2010年

6
您是什么意思“ STL不会自行抛出异常”?如果用超出范围的索引调用vector :: at怎么办?您还可以配置IO流以引发异常。此外,模板生成的代码比手工编写的代码要多。请参阅Stroustrup中的示例,其中包含将模板与void *组合以减少此类膨胀的情况。
Brian Neal

4
@Brian:vector::at()是一个很好的例子。准确地说,STL的使用方式将不会产生异常(在此,通过使用operator[]()代替at()),并且不会做出任何其他妥协。
j_random_hacker 2010年

@Brian:关于代码膨胀,如果您为编译器指定/ Gy并为链接器指定/ OPT:ICF,则在链接时使用MSVC ++将删除包含相同目标代码的函数。我相信GNU链接器可以做很多相同的事情。
j_random_hacker 2010年

1
@Brian Neal,我忘记了vector::at,也可能忘记了一些-感谢您的澄清。应该可以在标准库文件中搜索“ throw”,并找到我过度概括的语句的所有“例外”。
Mark Ransom

8

开源项目“嵌入式模板库(ETL)”通过提供/实现一个库来解决嵌入式应用程序中使用的STL的常见问题:

  • 确定性行为
  • “创建一组在编译时确定大小或最大大小的容器。这些容器应在很大程度上与STL中提供的那些容器相同,并具有兼容的API。”
  • 没有动态内存分配
  • 无需RTTI
  • 很少使用虚拟功能(仅在绝对必要时使用)
  • 固定容量容器一套
  • 容器的缓存友好存储作为连续分配的内存块
  • 减少容器代码大小
  • 类型安全的智能枚举
  • CRC计算
  • 校验和和哈希函数
  • 变体=类型的安全联合的种类
  • 选择断言,异常,错误处理程序或不检查错误
  • 经过大量单元测试
  • 有据可查的源代码
  • 和其他功能...

您还可以考虑由ESR Labs提供的面向嵌入式开发人员的商业C ++ STL


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

  2. 对于异常,STL不会引发很多异常,通常,最常见的异常是:在内存不足的情况下,系统应该重置,因此可以在分配器中进行重置。其他如超出范围,则可以由用户避免。

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



3

它基本上取决于您的编译器和您拥有的内存量。如果您有超过Kb的内存,那么动态分配内存会很有帮助。如果没有将标准库中的malloc实现调整为适合您的内存大小,则可以编写自己的内存,或者周围有不错的示例,例如Ralph Hempel的mm_malloc,可用于在顶部编写new和delete运算符。

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

必须记住,STL分配器将以2的幂数增加其分配,这意味着有时它会进行一些重新分配,直到达到正确的大小为止,您可以通过保留来防止它重新分配,因此它变得与所需的一个malloc一样便宜。如果您知道要分配的大小,则选择size。

例如,如果向量中有很大的缓冲区,则在某个时候它可能会进行重新分配,并最终在重新分配和移动数据时消耗您打算使用的内存大小的1.5倍。(例如,在某个时刻分配了N个字节,您可以通过append或插入迭代器添加数据,并且分配2N字节,复制前N个并释放N。在某个时刻分配了3N个字节)。

因此,最终它具有很多优势,而且如果您知道自己在做什么,就会付出很多。您应该对C ++如何在嵌入式项目上使用它毫不意外地有所了解。

对于固定缓冲区并进行重置的人,您始终可以在new运算符内部进行重置,或者如果内存不足,则可以进行重置,但这意味着您设计不当会耗尽内存。

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>

看起来并不那么可怕,并且如果未引发异常,则{}块或函数内不会添加任何开销。


1

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

我会认真研究如何通过覆盖新的/删除的运算符来创建自己的内存管理。我敢肯定,只要花一点时间,它就可以完成,这几乎是值得的。

至于例外问题,我不会去。异常是代码的严重减慢,因为它们导致每个块({ })之前和之后都有代码,从而允许捕获异常并破坏其中包含的任何对象。我手头上没有硬数据,但是每当我看到这个问题出现时,我就看到了压倒性的证据,表明使用异常会导致速度大幅下降。

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

异常处理会使您的代码变慢的原因是,编译器必须确保{}从抛出异常的地方到要处理的地方,每个块()必须释放其中的任何对象。这是添加到每个块的代码,而不管是否有人抛出异常(因为编译器无法在编译时告知该块是否将成为异常“链”的一部分)。

当然,这可能是一种旧的处理方式,在较新的编译器中已经变得更快(我对C ++编译器的优化不是最新的)。最好的了解方法就是运行一些示例代码,并打开和关闭异常(并包括一些嵌套函数),并计算差值。


6
-1表示完全不了解如何实现异常。
Billy ONeal 2010年

3
由现代编译器实现的异常通常不会导致运行时开销,除非实际抛出异常。而且,如果您谨慎地使用异常(而不是正常的流量控制),则在出现问题时性能不会成为问题。
Brian Neal

3
你有时间吗,布莱恩?我上次尝试对其进行测量(去年夏天)时,我发现仅在编译器设置中启用异常和堆栈展开会导致速度降低,无论我是否实际抛出了异常。
Crashworks'2

2
@Brian:至少在Win32上,每个try块都必须EXCEPTION_REGISTRATION在堆栈上设置一个块,并将FS寄存器指向该块。无论是否实际发生任何异常,都会发生这种情况。来源:microsoft.com/msj/0197/exception/exception.aspx 编译器还必须向每个使用非平凡析构函数声明任何对象的块添加代码,除非可以证明该块内不会发生异常。否则,如何在堆栈展开期间销毁这些对象?
j_random_hacker 2010年

3
@Brian:有趣的是,我只是在Linux x86 g ++ 4.2.1上尝试了我的pastebin片段的变体,值得称赞的是,唯一的区别是在堆栈上分配了额外的32个字节-但未写入。因此,似乎在一个函数中,如果有任何局部变量不适合寄存器(这意味着无论如何必须在堆栈上分配空间),如果没有捕获或抛出异常,则不会执行任何其他指令。非常令人印象深刻!
j_random_hacker 2010年

1

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


+1好点。“嵌入式”项目中的处理器正在变得越来越强大。我目前的Atmel处理器是32位的UC3系列。当我刚开始的时候,嵌入式意味着4或8位。但是,32位处理器只有512kB用户内存,这使事情有些紧张。您没有任何记忆问题吗?
Mawg说恢复Monica
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.