实时环境中的exception仍然不受欢迎?
几年前,我被告知,在embedded式系统或(非Linux)内核开发C ++ – 例外 的实时应用程序是不受欢迎的。 (也许这个教训是从gcc-2.95之前的)。 但是我也知道,exception处理已经变得更好了。
那么,在实际应用环境中, C ++就是例外情况
- 完全不需要的?
- 甚至通过编译器开关closures?
- 或非常仔细可用?
- 或者现在处理得如此之好,以至于几乎可以自由使用它们,还有一些事情要考虑?
- C ++ 11是否改变了这一点?
更新 :exception处理是否真的需要启用RTTI (就像一个回答者build议的那样)? 是否有dynamic演员参与,或类似?
exception现在已经被很好的处理了,实现它们的策略使得它们实际上比testing返回代码更快,因为只要不抛出任何东西,它们的成本(速度)几乎为零。
但是,他们的代价是:代码大小。 exception通常与RTTI一起工作,不幸的是RTTI与其他C ++function不同,因为您可以为整个项目激活或停用RTTI,一旦激活,它将为任何恰好具有虚拟方法的类生成补充代码,从而无视“你不用自己不用心的东西”。
另外,它的处理需要补充代码。
因此,例外的成本应该不是以速度来衡量,而是以代码增长来衡量。
编辑 :
来自@Space_C0wb0y
:这篇博客文章给出了一个小小的概述,并介绍了两种实现exception跳转和零成本的广泛方法。 顾名思义,好的编译器现在使用零成本机制。
关于exception处理的维基百科文章讨论了使用的两种机制 。 零成本机制是桌面驱动的机制。
编辑 :
从@Vlad Lazarenko
的博客@Vlad Lazarenko
,抛出exception的存在可能会阻止编译器内联和优化寄存器中的代码。
只是为了更新而回答:
exception处理是否真的需要启用RTTI
实际上,exception处理需要一些比RTTI更强大的dynamic强制转换。 考虑下面的代码:
try { some_function_in_another_TU(); } catch (const int &i) { } catch (const std::logic_error &e) {}
因此,当其他TU中的函数抛出时,它将查找堆栈(或者立即检查所有级别,或者在堆栈展开期间一次检查一个级别,这取决于执行情况),以便匹配对象的catch子句被抛出。
为了执行这个匹配,可能不需要在每个对象中存储types的RTTI方面,因为抛出exception的types是throwexpression式的静态types。 但是它确实需要以instanceof
方式比较types,并且需要在运行时进行,因为some_function_in_another_TU
可以在任何地方被调用,任何types的catch都可以被调用。 与dynamic_cast
不同的是,它需要对没有虚拟成员函数的types执行此运行时instanceof检查,对于那些不是类types的types。 最后一部分不会增加难度,因为非类types没有层次结构,所以只需要types相等,但是仍然需要types标识符,可以在运行时进行比较。
所以,如果你启用了exception,那么你需要进行types比较的RTTI部分,比如dynamic_cast
的types比较,但是包含更多的types。 您不一定需要RTTI的一部分,它将用于执行此比较的数据存储在每个类的vtable中,从对象可访问的数据 – 数据可能只能在每个throwexpression式的位置进行编码,而每个catch子句。 但是我怀疑这是一个很大的节约,因为typeid
对象不是很大,它们包含一个在符号表中经常需要的名字,加上一些实现定义的数据来描述types层次结构。 所以可能你也可以拥有RTTI。
exception问题不一定是速度(根据实施情况可能差别很大),但实际上是这样。
在实时的世界里,当你对操作有一个时间限制的时候,你需要确切的知道你的代码是干什么的。 exception提供了可能影响代码整体运行时间的快捷方式(exception处理程序可能不适合实时约束,或者由于例外情况,您可能根本不返回查询响应)。
如果你的意思是“实时”,实际上是“embedded”,那么代码的大小,如上所述,成为一个问题。 embedded代码可能不一定是实时的,但它可能有大小的限制(通常是)。
另外,embedded式系统通常被devise为在无限的事件循环中永远运行。 exception可能会带你到那个循环之外的地方,并且还会破坏你的内存和数据(因为堆栈解除),这又取决于你对它们做了什么,以及编译器是如何实现它的。
所以比对不起更安全:不要使用例外。 如果你能维持偶然的系统故障,如果你在一个单独的任务中运行,而不是很容易重新启动,如果你不是真正的实时,只是假装 – 那么你可以试试看。 如果您正在为心脏起搏器编写软件 – 我宁愿检查返回代码。
C ++exception仍然不被每个实时环境所支持,使得它们在任何地方都是可以接受的。
在video游戏的特定例子中(每帧都有16.6ms的截止date),主要的编译器实现C ++exception,只要在你的程序中打开exception处理就会显着减慢并增加代码的大小你是否真的抛出exception。 鉴于游戏机的性能和内存都是至关重要的,这是一个不争的事实:例如,PS3的SPU单元在代码和数据上都有256kb的内存!
除此之外,抛出exception仍然非常缓慢(如果你不相信我的话就会测量它),并且可能导致堆释放,这在你还没有剩余微秒的情况下也是不可取的。
我看到这个规则的一个…呃exception是每个应用程序运行一次就会抛出exception的情况 – 不是每帧一次,而是一次 。 在这种情况下,结构化的exception处理是在游戏崩溃时从操作系统捕获稳定性数据并将其传递给开发人员的可接受方式。
抛出exception时,exception机制的执行通常非常缓慢,否则使用exception机制的成本几乎没有。 在我看来,如果正确使用它们,exception是非常有用的。
在RT应用程序中,只有当程序不得不停止并修复问题(可能要等待用户交互)时,才会抛出exception。 在这种情况下,解决问题需要更长的时间。
例外情况提供了报告错误的隐藏path。 他们使代码更短,更可读,因此更容易维护。
C ++exception处理的典型实现仍然不理想,并且可能导致整个语言实现对于资源非常有限的一些embedded式目标几乎不可用,即使用户代码没有明确使用这些特征。 这被WG21最近的论文称为“零开销原则违反”,详见N4049和N4234 。 在这样的环境中,exception处理不能按预期工作(消耗合理的系统资源),无论应用程序是否是实时的。
但是,在embedded式环境中应该有实时应用程序能够承受这些开销,例如手持设备中的video播放器。
应该总是谨慎使用exception处理。 在任何平台(不仅适用于embedded式环境)的实时应用程序中,每帧中抛出和捕获exception是一个糟糕的devise/实现,而且通常是不可接受的。
还有一个例外的缺点。
exception通常很好,容易处理自动内存pipe理的语言(如C#,Python等)
但是在C ++中,大多数情况下,人们必须控制内存分配和对象的释放(new和delete),在很多情况下,exception变得非常棘手。 当发生exception时,通常需要释放先前分配的资源。 而在某些情况下,为它select合适的时刻和地点是非常棘手的。 而像自动指针这样的东西只能在某些情况下保存。
内存泄漏,段错误或不可预知的行为可能是exception抛出C ++期间不正确的对象/内存处理的结果。 这导致开发和debugging真正棘手的错误。
embedded式/实时开发通常有3或4个约束 – 特别是当这意味着内核模式开发
-
在不同的地方 – 通常在处理硬件exception时 – 操作不得抛出更多的硬件exception。 c ++的隐式数据结构(vtable)和代码(默认的构造函数&操作符和其他隐式生成的代码来支持c ++exceptionmechanisim)是不可放置的,因此不能保证在这个上下文中执行时放置在非分页内存中。
-
代码质量 – 一般来说,c ++代码可以隐藏很多复杂的语句,这些代码看起来微不足道,使得代码很难目测审计错误。 exception将处理与位置分离,使testing的代码覆盖范围变得困难。
-
C ++公开了一个非常简单的内存模型:新的分配从一个无限的空闲的存储,直到你用完,它会抛出一个exception。 在内存受限的设备中,可以编写更高效的代码,以明确使用固定大小的内存块。 对几乎所有的操作,C +的隐式分配使得不可能审计内存的使用。 此外,大多数c ++堆展示了令人不安的性质,即存储器分配可以花费多长时间没有可计算的上限 – 这又使得难以certificatealgorithm对需要固定上限的实时设备的响应时间。