Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the possible error handling strategies using Exceptions in C++, and what are their consequences and implications?

I would like your help in understanding what are the possible approaches to using/disabling exceptions in C++.

My question is not about what is the best choice but just about what are the possible options and what these options imply.

Currently, the options I can think of are:

  1. Compiling with -fno-exceptions and giving up most std containers (possibly defining internal containers which do not throw, such as suggested in SpiderMonkey Coding_Style)
  2. Just avoiding to throw and catch in own code, but still using std containers which may throw exceptions. Being happy with the fact that, in the case of exceptions, the program may terminate without stack unwinding, and that even RAII handled external resources may be left hanging. (This seems to be Google C++ approach according to answers to this SO question)
  3. No using exceptions but wrapping all in a catch all std::exception try block just to make sure stack is unwound and RAII handles to external resources are released before program is terminated, as for this Cert C++ rule
  4. As above, but also throwing exceptions which will ultimately result in program termination.
  5. Also using catched exceptions and recovering from exceptions.

I would like to know if my understanding of options is correct, and what I might be missing out or understanding wrong.

I would also like to know whether constraints on granting basic exception safety make sense for options 2-4 (where exceptions always ultimately lead to program termination) or if/how exception safety requirement can be relaxed/restricted to specific cases (e.g. handling external resources, files).

like image 591
Gianni Avatar asked Feb 20 '19 09:02

Gianni


People also ask

How errors and exceptions are handled in C?

The C programming language does not support exception handling nor error handling. It is an additional feature offered by C. In spite of the absence of this feature, there are certain ways to implement error handling in C. Generally, in case of an error, most of the functions either return a null value or -1.

What are the types of errors in exception handling?

There are three types of errors in programming: (a) Syntax Errors, (b) Runtime Errors, and (c) Logical Errors.

How many types of exception handling are there in C?

There are two types of exceptions: a)Synchronous, b)Asynchronous (i.e., exceptions which are beyond the program's control, such as disc failure, keyboard interrupts etc.).


1 Answers

Update

Strictly speaking, when you want to disable exceptions only compiling with no exception support is true way to go, so Option 1. Because when you want to disable exceptions you should also not use or even handle them. Raising an exception would terminate or go to a hard faulty on most implementations instantly, and only by this you would avoid overhead implications like space or performance overhead even how small (see below) they are.

However if you just want to know which paradigms about exception usage are out there, your options are almost right, with the general idea you haven't mentioned of keeping exceptions exceptional and doing things that are likely or even just could throw, at program startup.

And even more in general, in comes to error handling in general: Exceptions are there to handle errors which you come across at runtime and if done correctly errors you can ONLY detect at runtime. You can avoid exceptions by simply making sure to resolve all problems you can detect before runtime, like proper coding and review (code time), using strict types and checks (templates , compile time), and rechecking them again (static analyser).

Update 2

If you understand you wrong about caring about exception safety I would say: Basically at first it depends whenever you enable exceptions in general: If they are disabled you can't and shouldn't care about exception safety since there are none (or if they try to come into existence, you will terminate/crash/hardfault anyway). If you enable exceptions but don't use them in your code, like in case 2, 4 and 3, no problem you want to terminate anyway, so missing cleanup code is not relevant (and the stuff in 3. gets still run in any case). But then should make it clear to everybody that you don't want to use them, so they won't try to recover from exceptions. And if you use them in your code, you should care about exception safety in the way, that when an exception gets thrown, you clean up after your self too, and then its up the main handler or future code changes, when ever you still terminate or can recover. Not cleaning up but still using exception wouldn't make sense. Then you can just stick to option 1.

This should be to my knowledge exhaustive. For more information see below.

Recommendation

I recommend option 4. and I tell you why:

Exceptions are a very misunderstood and misused pattern. They are misused for program flow like the languages Java does in a excessive. They are often forbidden in embedded or safety code because of their nature that is hard to tell which handler will catch it, if there is one, and how long this will take, for which the C++ std just says basically "implementation magic".

Background

However in my reasoning the hate for exceptions is basically a big XY problem: When people are complaining that their recovery is broken and hard to tell, then the usual problem is that the don't see you can't or should do much about the most exceptions, that's what they are for. However things like a timeout or a closed tcp connection are hardly non normal, but many people use exceptions for that, that would be wrong of course. But if your whole OS tells you that there is no network adapter or no more memory what could you do? The only thing you probably want is trying to log the reason somewhere and this should be done in one try/catch around the main block.

The same is for safety/real-time programs: For the example of running out of memory, when that happens you are **** anyway, the strategy then is to do this stuff at an unsafe initialisation time, in which exceptions are no problem too.

When using containers with throwing members its a similar scenario, what would you be able to do when you would get their error code instead? Not much, thats the reason why you would make sure at code time that there is no reason for errors, like making sure that an element is really in there, or reserving capacity for your vectors.

This has the nice benefit of cleaner code, not forgetting to check for errors and no performance penalty, at least using the common C++ implementations like gcc etc.

The reason of 3. is debatable, you could do this in my opinion too, but then my question is: What do you have to do in the destructors which wouldn't cleanup your operating system anyway? The OS would cleanup memory, sockets etc.. If have a freestanding scenario, the question remains, whenever different: You plan to halt because your UART broke for example, what would you like to do before halting? And why can't you do it in the catch block before rethrow?

To sum up

  1. Has the problem of not using any throwing code, and still be left with the problem of how to handle rare error codes. (Thats why so many C programmers still use goto or long jumps)

  2. Not viable IMHO, worst of both

  3. as mentioned ok, but what do you need to do in your static DTors, what you even un-normal termination wouldn't do?

  4. My favourite

  5. Only if you really have rare conditions that you are actual able to recover from

What I mean as a general advice: Raising and exception should mean that something happened that should never happen in normal operation, but happened due to a unforeseeable fault, like hardware fault, detached cables, non found shared libraries, a programming fault you have done, like trying to .at() at an index not in the container, which you could have known of, but the container can not. Then it is only logical that throw an exception should almost every time lead to program termination/hard fault

As a consequence of compiling with exception support, is that for example for a freestanding ARM program, your program size will increase by 27kB, probably none for hosted (Linux/Windows etc.), and 2kB RAM for the freestanding case (see C++ exception handler on gnu arm cortex m4 with freertos )

However you are paying no performance penalty for using exceptions when using common compilers like clang or gcc, when you code runs normal i.e. when your branches/ifs which would trow an exception, are not triggered.

As a reference i.e. proof to my statements I refer to ISO/IEC TR 18015:2006 Technical Report on C++ Performance, with this little excerpt from 7.2.2.3:

Enable exception handling for real-time critical programs only if exceptions are actually used. A complete analysis must always include the throwing of an exception, and this analysis will always be implementation dependent. On the other hand, the requirement to act within a deterministic time might loosen in the case of an exception (e.g. there is no need to handle any more input from a device when a connection has broken down). An overview of alternatives for exception handling is given in §5.4. But as shown there, all options have their run-time costs, and throwing exceptions might still be the best way to deal with exceptional cases. As long as no exceptions are thrown a long way (i.e. there are only a few nested function calls between the throw-expression and the handler), it might even reduce run-time costs.

like image 195
Superlokkus Avatar answered Sep 29 '22 11:09

Superlokkus