为什么嵌入式平台开发人员会继续尝试从他们的SDKs中删除C++ exceptions的使用?
例如,Bada SDK建议对异常用法进行以下变通,这看起来异常丑陋:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| result
MyApp::InitTimer()
{
result r = E_SUCCESS;
_pTimer = new Timer;
r = _pTimer->Construct(*this);
if (IsFailed(r))
{
goto CATCH;
}
_pTimer->Start(1000);
if (IsFailed(r))
{
goto CATCH;
}
return r;
CATCH:
return r;
} |
这种行为的原因是什么?
据我所知,ARM编译器完全支持C++ exceptions,这实际上不是问题。还有什么?ARM平台上的异常使用和解除开销是否真的如此之大,以至于花费大量时间来解决这些问题?
也许还有什么我不知道的?
谢谢您。
- +我把它描述得异常丑陋…
- 一个重要原因是旧代码。除非代码从一开始就是安全的,否则它不是安全的。这是谷歌为什么不使用例外的一个重要原因:没有从一开始,现在我们有点坚持这个决定。
- 我建议把"usage"标签(对我来说似乎是一个禁止操作)改为"embedded"。
- 您的意思是"为什么他们不允许平台中的异常"还是"为什么人们不使用异常"的更一般的方式?对于前者,禁用异常是确保使用"嵌入式C++"子集与平台兼容的途径。en.wikipedia.org/wiki/embedded_c%2b%2b
- 这里有很多答案。有一篇关于例外的文章。setjmp()和longjmp()更受控制。每个对象常常被输入到异常表中,并且在每个文件编译中计算出表是非最佳的。通常,如果它位于磁盘上,这不是一件痛苦的事。嵌入式应用程序通常没有磁盘。即使在今天(2013年),g++开发人员仍在尝试优化这些表。在某些情况下,它们可以和代码一样大!
只是我的2美分…
我专门咨询嵌入式系统,其中大多数都是硬实时和/或安全/生命关键的。它们中的大多数运行在256K或更低的闪存/ROM中——换句话说,它们不是"PC式"的VME总线系统,具有1GB+的RAM/闪存和1GHz+的CPU。它们是深度嵌入的,有点资源限制的系统。
我想说,至少75%的使用C++的产品在编译器中禁用异常(即,用禁用异常的编译器开关编译的代码)。我总是问为什么。信不信由你,最常见的答案不是运行时或内存开销/成本。
答案通常是混合的:
- "我们不相信我们知道如何编写异常安全代码"。对他们来说,检查返回值更熟悉、更不复杂、更安全。
- "假设您只在异常情况下抛出异常,则在这些情况下,我们无论如何都会重新启动[通过它们自己的关键错误处理程序例程]。"
- 遗留代码问题(正如JALF所提到的)-他们使用的代码始于许多年前,当时他们的编译器不支持异常,或者没有正确或有效地实现异常。
而且-经常会有一些关于开销的模糊的不确定性/恐惧,但几乎总是没有量化/未归档的,只是有点像从表面上看的那样。我可以向您展示声明异常开销为3%、10%-15%或~30%的报告/文章。人们倾向于引用这个数字来表达他们自己的观点。几乎总是,这篇文章过时了,平台/工具集完全不同,等等。正如Roddy所说,您必须在平台上衡量自己。
我不一定要捍卫这些立场,我只是给你真实的反馈/解释,我听到许多公司的工作与C++在嵌入式系统上,因为你的问题是"为什么这么多的嵌入式开发人员避免异常?"
- 我也有自己的想法和经验朝着这个方向前进,我会补充说,任何添加到项目中的代码/二进制文件都会增加风险。如果代码的附加值不能补偿验证代码所需的风险和质量保证周期,那么就不要添加它。嵌入式系统希望比桌面系统更可靠,任何一行代码或库blob链接、使用或不使用都会增加您的风险。
我可以想到几个可能的原因:
- 旧版本的编译器不支持异常,因此编写了许多代码(并建立了约定),其中不使用异常
- 异常确实有一定的成本,它可能高达总执行时间的10-15%(它们也可以实现为几乎不花时间,但使用相当多的内存,这在嵌入式系统上也可能不是很理想的)
- 嵌入式程序员往往对代码的大小、性能和(尤其是)代码的复杂性有点偏执。他们经常担心"高级"功能可能无法与编译器一起正常工作(而且他们也经常是正确的)
- 你能提供10-15%的参考值吗?或者他们需要很多记忆?
- EDA QA C++性能报告:www.研究. Attg//bS/PrimeStudi.PDF,虽然它给出了不同的数字。
- 哪个部分包含异常处理的编号?在2.4中,它们拼出了差异/细节,但似乎没有给出实际的比较数字。
- 我总是想知道这些开销声明:开销与什么相比?它是否使用了15%以上的内存,在平均用例中运行的速度慢了15%,或者在实际触发异常的用例中运行的速度慢了15%?还有什么其他的错误捕捉技术比较呢?转到os/errno/…?因为它们都有自己的程序设计影响,也会影响"开销"(这样一个通用术语…)
- @埃达:请注意,我没有说"他们永远都会接受",只是说可能有那么多。我这里没有数据来源,但是是的,我已经看到一些基准测试显示了在这个范围内的性能冲击。但这显然很大程度上取决于具体的实现。我不是说"例外很慢",而是说"例外很慢"。至于"记忆的很多"部分,对于"很多"的适当定义来说,这是正确的。基于表的方法(基本上将指令指针值映射到包含静态异常信息的表条目)占用空间,以避免速度下降
- @基里安:我完全同意,是的,这取决于很多(在本例中没有具体说明)的假设和背景,当我看到"10-15%的衡量标准"时,我有一些反对意见。但请记住,我正在讨论最坏的情况。他们可能不鼓励使用异常,因为他们的实现不好/效率低下,或者因为他们担心性能受损的病理情况会太频繁地触发。
- 我的印象是,使用异常会导致0性能损失,除非实际抛出了异常,此时,谁关心性能,因为抛出了错误。
- @布鲁拉贾:有时候是这样的。这取决于如何实现异常(零成本实现基于静态生成的表,这些表占用了额外的内存,因此需要权衡,尤其是在内存受限的平台上)。
我想这几天大部分都是软糖。
在创建具有构造函数/析构函数的对象的块的入口和出口处,异常确实有很小的开销,但在大多数情况下,这实际上不应该是一罐bean。
先量后优。
但是,引发异常通常比返回布尔标志慢,因此只针对异常事件引发异常。
在一个案例中,我看到每当抛出异常以供潜在的调试使用时,RTL都在从符号表构建整个可打印的堆栈跟踪。你可以想象,这不是件好事。这是几年前的事了,当这件事曝光时,调试库被匆忙修复了。
但是,在IMO中,正确使用异常可以获得的可靠性远远超过轻微的性能损失。使用它们,但要小心。
编辑:
@JALF提出了一些很好的观点,我上面的回答是针对一个相关的问题,即为什么许多嵌入式开发人员通常仍然轻视异常。
但是,如果一个特定的平台SDK的开发人员说"不要使用异常",那么您可能必须这样做。可能他们的库或编译器中的异常实现存在特定的问题,或者他们担心回调中抛出的异常,这会导致由于他们自己的代码中缺乏异常安全性而产生问题。
- "所以只对异常事件抛出异常。"+1
- 请记住,我们讨论的是嵌入式平台,在这些平台上,异常的实现可能不如在更主流的平台上那么优化。
- 另外,你没有回答这个问题。它不是"gotos邪恶,或者我应该使用异常",而是"为什么许多嵌入式SDK不鼓励使用异常?"
- Jalf:"问题"实际上是"C++异常开销",它可能没有那么有用:-(在平台问题上的公平点)。将编辑…
- "先测量,后优化。"+1
与"哥特人是邪恶的"相反的观点在其他答案中得到了支持。我创建这个社区wiki是因为我知道这个相反的观点会被激怒。
任何值得一试的实时程序员都知道goto的这种用法。它是一种广泛使用和广泛接受的错误处理机制。许多硬实时编程环境不实现。在概念上,例外只是setjmp和longjmp的受限版本。那么,当底层机制被禁止时,为什么要提供异常呢?
如果始终可以保证在本地处理所有抛出的异常,则环境可能允许异常。问题是,为什么要这样做?唯一的理由是哥特人总是邪恶的。嗯,他们并不总是邪恶的。
- 我想这也有点道理。我认识一些有才华的程序员,他们有相同的观点,尽管我可能不同意你的观点,但是其他的观点总是有用的。
- 通过这种逻辑,C++不应该提供函数调用或控制逻辑。因为它们基本上只是"聪明"的跳跃,也就是gotos,也就是"被禁止的"
- 好吧,异常实际上只是goto的一个抽象,它的优点是一路自动进行raii清理:)
- 什么RAII清理?这是一个关于实时编程环境的问题。这里没有一个AutoTypTR,一个向量,一个C++流。当您切换到实时编程模式时,您必须放弃许多标准编程模式的思考方式。
- 只是一个提示,但不要将其他答案称为"以上"或"以下",因为它们是按投票数动态排序的,所以现在位于您上方的帖子可能会在明天低于您。:)为了避免混淆,我通常用作者的名字来指代其他答案。
- @大卫:真的吗?哪些平台完全支持异常(这是OP提出的问题),但使RAII不可能实现?
- @戴维。RealTimes是一个广泛的教会:我已经在汇编程序、PASCAL、C、C++中从PICS向上编程了30年的硬实时系统。您可以使用对您的需求有意义的语言功能来简化工作。RAII和模板在许多情况下都是混合的一部分。
- 是的,只是和一个年轻的同事长时间讨论了为什么我不应该用goto来处理嵌入式系统中的错误。我试着向他解释,例外(以及休息和返回)都只是口红的Gotos。最终,重新构造代码比继续和他争论更容易。
- 为什么是USW例外?因为它们允许将错误处理代码与正常程序逻辑分开,并在正确的代码抽象层处理错误。为什么在函数调用树路径上的每个代码块大多数时间都无法正常处理时,都要强制向上处理错误处理代码(使用一些if-return组合向上传播错误信息到调用函数)?
- @Jonnydee:这个问题是关于嵌入式系统的,这是一个远离大多数编程问题的世界。任何能在夜间颠簸的东西都需要保护,而且保护的性能成本需要很小,并且需要可预测。展开堆栈既昂贵又不可预测,因此它在嵌入式系统中被广泛禁止(但并非普遍禁止),特别是在硬实时嵌入式系统中。
- @达维达曼我同意,"嵌入式系统"是另一个世界。但我的评论是对这个问题的一个回答:"如果所有抛出的异常都能保证在本地处理,那么环境可能允许异常。"问题是,为什么要这样做?"他问为什么要使用异常,即使满足了使用异常的必要要求。
这种对待异常的态度与性能或编译器支持无关,而一切都与异常增加代码复杂性的想法有关。
据我所知,这个想法几乎总是一种误解,但由于一些不可想象的原因,它似乎有强有力的支持者。
- 另外,乔尔·斯波斯基(乔尔·斯波斯基的一部分已经失去了他的心智系列)
- 如果复杂性是由循环复杂性度量的,那么异常会增加代码的复杂性。如果计算正确,函数抛出的每种异常类型都会增加该函数的圈复杂度两倍。
- @您需要将异常与其他错误报告方案进行比较,而不是无例外。
现代C++编译器可以将异常的运行时使用减少到开销的3%。尽管如此,如果极端的程序员发现它很昂贵,那么他们就会诉诸于这种卑鄙的把戏。
请参见bjarne strourstrup的页面,了解为什么使用异常?
- 我几乎不是一个世界级的嵌入式平台专家,但我相信他们很少有什么可以被认为是"现代C++编译器",使3%位数相当不相关。:)