嵌入式 C++ : 是否使用异常?
我意识到这可能是主观的,所以会问一个具体的问题,但首先是背景:
我一直是一名嵌入式软件工程师,但通常在 OSI 堆栈的第 3 层或第 2 层。我并不是一个真正的硬件专家。我一般都做电信产品,通常是手机/手机,这通常意味着像 ARM 7 处理器之类的东西。
现在我发现自己处于一个更通用的嵌入式世界中,在一家小型初创公司中,我可能会转向“不太强大”的处理器(有主观的部分) - 我无法预测哪个。
我读过很多关于嵌入式系统中 C++ 异常处理的争论,但没有明确的答案。对于可移植性有一些小担忧,还有一些关于运行时的小担忧,但它似乎主要归结为代码大小(或者我读错了辩论?)。
现在我必须决定是否使用或放弃异常处理 - 对于整个公司来说,永远(它会进入一些非常核心的软件)。
这听起来可能像是“一根绳子有多长”,但有人可能会回答“如果你的绳子是 8051,那就不要。如果,OTOH,它是……”。
我该往哪个方向跳?超级安全&失去一个好的功能,或者特殊的代码,以后可能会遇到问题?
I realize this may be subjective, so will ask a concrete question, but first, background:
I have always been an embedded software engineer, but usually at Layer 3 or 2 of the OSI stack. I am not really a hardware guy. I have generally always done telecoms products, usually hand/cell-phones, which generally means something like an ARM 7 processor.
Now I find myself in a more generic embedded world, in a small start-up, where I might move to "not so powerful" processors (there's the subjective bit) - I cannot predict which.
I have read quite a bit about debate about exception handling in C++ in embedded systems and there is no clear cut answer. There are some small worries about portability and a few about run-time, but it mostly seems to come down to code size (or am i reading the wrong debates?).
Now I have to make the decision whether to use or forego exception handling - for the whole company, for ever (it's going into some very core s/w).
That may sound like "how long is a piece of string", but someone might reply "if your piece of string is an 8051, then don't. If, OTOH, it is ...".
Which way do I jump? Super-safe & lose a good feature, or exceptional code and maybe run into problems later?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
在性能方面,我的理解是,异常实际上减少了代码正常执行路径的大小并提高了性能,但使异常/错误路径的成本更高。 (通常要贵很多)。
因此,如果您唯一关心的是性能,我会说以后不要担心。如果今天的 CPU 能够处理它,那么明天也能处理。
但是。在我看来,异常是要求程序员始终比合理预期的更聪明的功能之一。所以我说 - 如果你可以远离基于异常的代码。远离。
看看 Raymond Chen 的更干净、更优雅、更难以识别。他说得比我好。
In terms of performance, my understanding is that exceptions actually reduce the size and increase the performance of the normal execution paths of code, but make the exceptional/error paths more expensive. (often a lot more expensive).
So if your only concern is performance, I would say don't worry about later. If today's CPU can handle it, then tomorrows will as well.
However. In my opinion, exceptions are one of those features that require programmers to be smarter all of the time than programmers can be reasonably be expected to be. So I say - if you can stay away from exception based code. Stay away.
Have a look at Raymond Chen's Cleaner, more elegant, and harder to recognize. He says it better than I could.
异常的最大问题是——它们没有可预测的执行时间。
因此它们不适合硬实时应用程序(我猜大多数嵌入式应用程序不属于这一类)。
第二个是(可能)增加二进制文件的大小。
我建议您阅读C++ 性能技术报告其中专门讨论了您感兴趣的主题:在嵌入式(包括硬实时系统)中使用 C++ 以及异常处理通常如何实现以及它有哪些开销。
The most problem with exceptions -- they don't have predictable time of execution.
Thus they are not suitable for hard real-time applications (and I guess most embedded application doesn't fall in this category).
The second is (possible) increasing of binary's size.
I would propose you reading of Technical Report on C++ Performance which specifically addresses topics that you are interested in: using C++ in embedded (including hard real-time systems) and how exception-handling usually implemented and which overhead it has.
是否使用异常的选择实际上应该取决于它们是否能够很好地适合您的程序的问题领域。
我广泛使用了 C++ 异常,无论是在旧的 C 代码中还是在一些较新的代码中。 (提示:不要尝试重新调整 20 年前的 C 代码,这些代码是在低内存环境中编写的,并且存在各种不一致的异常。这只是一场噩梦)。
如果您的问题适合在一个位置处理所有错误(例如,某种 TCP/IP 服务器,其中每个错误条件都满足“断开连接并重试”),那么例外是好的- 你可以在任何地方抛出异常,并且你知道它将在哪里以及如何处理。
另一方面,如果你的问题不适合中央错误处理,那么异常就是一个巨大的痛苦,因为试图找出某些东西在哪里(或应该)处理很容易成为一项西西弗斯式的任务。而且仅仅看代码确实很难看出问题所在。相反,您必须查看给定函数的调用树,并查看该函数的异常将在哪里结束,以便确定是否有问题。
The choice of whether to use exceptions or not should really lie with whether they are going to fit your program's problem domain well or not.
I've used C++ exceptions extensively, both in retrofitting into old C code, and in some newer code. (HINT: Don't try to re-fit 20 year old C code that was written in a low memory environment with all manner of inconsistent exceptions. It's just a nightmare).
If your problem is one that lends itself to handling all the errors in one spot (say, a TCP/IP server of some sort, where every error condition is met with 'break down the connection and try again'), then exceptions are good - you can just throw an exception anywhere and you know where and how it will be handled.
If, on the other hand, your problem doesn't lend itself to central error handling, then exceptions are a ROYAL pain, because trying to figure out where something is (or should be) handled can easily become a Sisyphean task. And it's really hard to see the problem just by looking at the code. You instead have to look at the call trees for a given function and see where that function's exceptions are going to end up, in order to figure out if you have a problem.
我认为问题在于许多人在没有充分理解 C++ 中的异常处理如何工作的情况下发表了自己的意见。
我最近在一家新公司开始工作,大家一致认为我们不应该使用异常,因为我们无法测试它们,因为不确定的行为等等。当然,都是错误的。
当我们谈论在代码中使用异常处理的开销时,我们需要仔细考虑什么开销?当然这是有代价的,但这就是替代方案的成本吗?假设我们正在使用返回错误代码。在这种情况下,源代码中的一半 if() 语句将专门用于测试错误代码并将其转发到调用堆栈。这不是我编造的,而是我正在查看的代码库的实际指标。其中大部分是为了处理a)几乎不可能发生的事件,例如嵌入式应用程序启动时没有内存,b)如果它们真的发生了,我们无论如何也无能为力,其他比关机。
在这种情况下,引入异常处理将显着降低代码的复杂性,将罕见事件的处理与常规业务逻辑分开。并且节省了大量的开销,因为错误代码检查一直在进行。此外,它还显着减少了代码的大小。想想需要付出多少努力才能通过 10 个不同的函数在堆栈中传播错误代码,直到我们到达可以正式退出的地方。使用异常处理可以绕过10个中间层的函数,直接进入我们可以处理的地方。
I think the problem is that many people voice their opinion without having a solid understanding of how exception handling in C++ works.
I have recently started at a new company, and there is consensus that we should not use exceptions, because we can't test them, because nondeterministic behaviour, etc etc. All wrong, of course.
When we talk about the overhead of having exception handling used in the code, we need to carefully consider overhead on top of what? Granted it comes at a cost, but that is the cost of alternative? Say we are using return error codes. In such scenario HALF of the if() statements in the source code will be dedicated to testing error codes and forwarding them up the call stack. Not making this up, actual metric of the codebase I'm looking at. And most of it is to deal with a) events that can't pretty much ever happen, like not having memory at the start of an embedded application and b) events that if they actually happen, we can not do anything about anyway, other than shutdown.
In this case introduction of exception handling would significantly reduce complexity of the code, separating handling of rare events from the business as usual logic. And saves ton's of overhead, because the error code checking is happening all the time. Further, it significantly reduces the size of the code. Think how much effort is needed to propagate error codes up the stack through 10 different functions, until we get somewhere where we can officially bail. Using the exception handling you can bypass the 10 intermediary levels of functions, and go straight to where we can deal with it.
我想说,如果运行时环境支持异常,请适当使用异常。处理异常情况的异常是可以的,并且根据实现的不同,可能会导致很少的开销。有些环境不支持它们,尤其是在嵌入式世界中。如果您禁止它们,请小心解释原因。我曾经有一个人,当被告知不要使用异常时,他用除零代替。不完全是我们的想法。
I'd say use exceptions appropriately if the runtime environment supports them. Exceptions to handle extraordinary conditions are fine, and can cause little overhead depending on the implementation. Some environments don't support them, especially in the embedded world. If you ban them, be careful to explain why. I once had a guy that, when told not to use exceptions, did a divide by zero instead. Not exactly what we had in mind.
我通常考虑两个用例,指导何时使用异常以及如何实现
有多种替代技术:
C 样式状态返回值或参数。这对代码的可读性有很大的影响,特别是当它涉及嵌套的 if 时。函数的实际逻辑可能会被错误检查 if/else 或函数的多次返回所淹没,这些函数被认为是禁忌,尤其是在存在诸如在函数出口处撤消锁定之类的包装代码的情况下。
C++ 异常 - 消除了 c 风格状态的可读性问题,但增加了额外的架构设计工作来决定架构的哪个级别应生成和处理异常。这也需要使用 C 风格的状态值来完成,但 C 具有轻微的工程优势,因为状态在 API 中可见,并迫使各个级别的程序员决定如何处理状态。如果您在 API 中看到有返回状态,那么至少要考虑它可以具有哪些值(希望有详细记录),考虑它是否会发生以及您可以采取哪些措施。使用 C++,异常抛出不太明显 - 您甚至看不到状态值返回或参数值。
一个折衷方案(我仍然需要尝试)是在函数中使用 C++ 异常 - 即在函数中抛出任何在 C 中设置并返回状态的位置 - 并在函数末尾使用 catch ,这样您有一个出口(即释放锁) - 这消除了代码中的嵌套 else 和/或多个返回。
如果您有关于如何设计传递和处理异常的架构设计 - 那就去做吧 - 如果您没有,那么使用 C++ 异常会比 C 风格的嵌套 if 和/或多个函数出口更简洁,并且可能更健壮。
I usually consider two use cases that guide when exceptions should be used and how they should be implemented
There are several alternative techniques:
C-style status return value or parameter. This has a heavy impact on code readability, especially when it involves nested ifs. The real logic of a function can be overwhelmed by error checking if/elses or multiple returns from a function that are considered a no-no, especially if there is wrap up code such as undoing a lock at the function exit.
C++ exceptions - eliminates the readability problems of the c style status, but adds an extra architectural design effort to decide which level of the architecture should generate and handle the exceptions. This needs to be done with C-style status values as well, but C has a slight engineering advantage since the status is visible in the API, and forces the programmer at every level to decide what to do with the status. If you see in the API that there is a return status, then as a minimum think about what values it can have (hopefully well documented), consider if it can happen and what you can do about it. With C++, exception throwing is less visible - you can't even see the status value return or parameter value.
A compromise (which I still need to try) is to use C++ exceptions within a function - i.e. throw anywhere in a function where in C you would set and return a status - and use a catch at the end of the function so that you have a single exit (i.e. to release a lock) - that eliminates nested elses, and/or multiple returns in the code.
If you have an architectural design for how to design pass and handle exceptions around - great go for it - if you don't then using C++ exceptions is much neater and probably more robust than C style nested ifs and/or multiple function exits.
这些天我在 ESP32S3 上做了很多事情。这一切都归结为您是否可以接受性能影响。免费异常处理的传说对于 PC 来说可能是正确的,但在嵌入式系统上这是一个神话:
在我的 ESP32S3 上,每次调用
try
/catch
会给您带来非常几纳秒的性能影响,但是实际抛出
的代码可能会带来至少额外的400μs(!)。虽然这对于 IRQ 服务例程来说是完全不可接受的,但我也会在驱动程序代码和频繁执行的所有内容中对此保持警惕。
I'm doing a lot on ESP32S3 these days. It all boils down to whether you can accept the performance impact. The legend of toll free exception handling may be true for PCs, but on embedded systems it's a myth:
On my ESP32S3 each invocation of
try
/catch
gives you a performance hit of very few nanoseconds, however code that actuallythrow
s may bring at least an additional 400µs(!) to the table.While this is completely unacceptable for IRQ service routines, I'd be wary of it also in driver code and everything that is executed frequently.