How is the C++ exception handling runtime implemented?
Asked Answered
P

4

95

I am intrigued by how the C++ exception handling mechanism works. Specifically, where is the exception object stored and how does it propagate through several scopes until it is caught? Is it stored in some global area?

Since this could be compiler specific could somebody explain this in the context of the g++ compiler suite?

Pleochroism answered 29/1, 2009 at 7:30 Comment(5)
Read this Article will help youHomeopathic
I don't know - but I'm guessing that the C++ spec has a clear definition. (I may be wrong though)Alloy
No, the spec doesn't give a definition. It dictates behavior, not implementation. Paul, you might want to specify which implementation you're interested in.Baran
Related question: #308110Gunfight
Refer to section 5.4 of Technical Report on C++ PerformanceSikko
S
60

Implementations may differ, but there are some basic ideas that follow from requirements.

The exception object itself is an object created in one function, destroyed in a caller thereof. Hence, it's typically not feasible to create the object on the stack. On the other hand, many exception objects are not very big. Ergo, one can create e.g a 32 byte buffer and overflow to heap if a bigger exception object is actually needed.

As for the actual transfer of control, two strategies exist. One is to record enough information in the stack itself to unwind the stack. This is basically a list of destructors to run and exception handlers that might catch the exception. When an exception happens, run back the stack executing those destructors until you find a matching catch.

The second strategy moves this information into tables outside the stack. Now, when an exception occurs, the call stack is used to find out which scopes are entered but not exited. Those are then looked up in the static tables to determine where the thrown exception will be handled, and which destructors run in between. This means there is less exception overhead on the stack; return addresses are needed anyway. The tables are extra data, but the compiler can put them in a demand-loaded segment of the program.

Subassembly answered 29/1, 2009 at 10:19 Comment(6)
AFAIR g++ uses the second, address-table approach, presumably for reasons of compatibility with C. Microsoft C++ compiler uses a combined approach, since its C++ exceptions are built on top of SEH (structured exception handling). In each C++ function, MSC++ creates and registers a SEH exception-handling record, which points to a table with address ranges for try-catch blocks and destructors in this particular function. throw packages a C++ exception as a SEH exception and calls RaiseException(), then SEH returns control to the C++-specific handler routine.Hypothalamus
@Anton: yes, it uses the address-table approach. See my answer to another question at #308110 for the details.Gunfight
Thanks for the answer. You can see how C purists could be terrified of C++ and its exceptions. The idea that a simple try/catch can unwittingly create a number of stack objects at runtime or bloat your program with extra tables is the reason why embedded systems often avoid them.Tad
@speedplane: No, that's more due to a lack of understanding. Error handling never is free. C just forces you to write it yourself. And we all know how many C programs are missing a free() or an fclose() in some rarely used code path.Subassembly
@Subassembly I'm not disagreeing, it's almost entirely a lack of understanding. Engineers often don't understand how exceptions work and how exceptions will affect their code, which then, justifiably, leads to hesitation when using exceptions. If exception handling implementation was more clearly communicated (and didn't seem like magic), many would be less hesitant to use them.Tad
I guessed the stack approach but missed the table approach. Stack is natural but table can be more sophisticated. Either way, I think the essential part is, to record where/who to catch the exception.Coreligionist
T
22

This is defined in 15.1 Throwing an exception of the standard.

The throw creates a temporary object.
How the memory for this temporary object is allocated is unspecified.

After creation of the temporary object control is passed to the closest handler in the call stack. unwinding the stack between throw and catch point. As the stack is unwind any stack variables are destroyed in reverse order of creation.

Unless the exception is re-thrown the temporary is destroyed at the end of the handler where it was caught.

Note: If you catch by reference the reference will refer to the temporary, If you catch by value the temporary object is copied into the value (and thus requires a copy constructor).

Advice from S.Meyers (Catch by const reference).

try
{
    // do stuff
}
catch(MyException const& x)
{
}
catch(std::exception const& x)
{
}
Templas answered 29/1, 2009 at 8:28 Comment(17)
Something else that's unspecified is how the program unwinds the stack and how the program knows where the "closest handler" is. I'm pretty sure Borland holds a patent on one method of implementing that.Baran
As long as the objects are destroyed in reverse order of creation the implementation details are not important unless you are a compiler engineer.Templas
Voted down: a) "Scott Meyers", not "S. Myers"; b) untrue citation: "Effective C++": "Item 13: Catch exceptions by reference.". This will allow for tweaking/appending info to the exception object.Heat
@phresnel: Don't forget Item 21: "Use const whenever possible". There is no good case for tweaking an exception. You should be a) "fixing and discarding", b)re-throwing or c)generating a new exception.Templas
@Martin: That's not what my post was about. You quoted wrongly, both with original author type ("Myers") as well as the wrong statement that "Myers" advises on using const&, but he does not. Neither in Effective C++, nor in C++ Coding Standards (Item 73: "Throw by value, catch by reference"). Also, const whenever possible is too extreme and not what "Myers"/Meyers advised. If you intend to tweak a value, const puts unecessary burdens on the compiler. And the way to tweak exceptions (e.g. to add contextual information upon exception-propagation) is non-const/polymorhpic reference ...Heat
@phresnel : There is no reason to make an exception non const. As there is no good reason to tweak the exception. Adding contextual information to the exception may seem like a good idea but in reality adds little of use at the burden of complicating your exceptions. In reality you will create a new exceptions if more information is needed. There is no burden on the compiler (that's just a silly statement) did you mean burden on the developer. I stand by original statement. 1) Catch by reference. 2) Use const whenever possible. Means catch by const reference whenever possible.Templas
@Martin: Burden: indeed const whenever possible puts burden, on both the compiler AND the programmer. Don't forget that C++ is an imperative language with state and explicit flow-control. In some situations, mutating an existing variable is simply the right thing to do; let's just start with the common for-loop and the iterator. It is possible to make loops purely const, right, by doing them via recursion, but well; same with number crunching and huge arrays; expression templates to a lot, but if flow-control reaches a critical mass, it becomes an optimization burden to the compiler ...Heat
@Martin: ... I could construct endless real life examples; my advice: const by default and where reasonable, and I am aware that reasonable is relative. //// Exceptions) Well, I guess than this is personal taste. You have reasons, I (and Meyers, Alexandrescu and Sutter) have reasons, too. I just find it hard to justify to construct a completely new execption if it basically a slightly modified copy of what was caught. Imho, that's premature pessimization, as is const whenever possible.Heat
@Martin: Just as a side note, I've written a compile time ray tracer where indeed everything is const, so const that everything resides in enums and types. Perfectly possible, but for most programmers it's write only code (have a look at your copy of Effective STL about Read Only Code). So you see I know what I talk about. See gitorious.org/metatrace .Heat
@phresnel: Yes you have your reasons (don't agree with your logic), I have mine and though I will not claim to have talked to them about this specific subject or actually know their minds (Meyers, Alexandrescu and Sutter) I believe my interpretation stands valid. But if you are in the Seattle area then you can speak with all three as they are regular attendees at the North West C++ User Group (Meyers less often than the others).Templas
Why do C++ programmers trust a mechanism for exceptions that the standard does not enforce implementation of? How can you be sure your exceptions behave in trustworthy ways if you cannot inspect the exceptions? Clearly the exceptions must be a runtime construct, and as such the exception objects must be inspectable at runtime, but is there any standard or API for inspecting the exceptions? If not; why don't we just use functions with error callbacks and default to(if no callback is given) not blowing up our program each time an exception is thrown?Decaliter
Why do C++ programmers trust a mechanism for exceptions that the standard does not enforce implementation of? The C++ standard defines behaviors not implementation techniques. It is up-to the implementation to make sure it conforms to the standard.Templas
@Dmitry How can you be sure your exceptions behave in trustworthy ways if you cannot inspect the exceptions? You can. You catch the exception.Templas
@Dmitry Clearly the exceptions must be a runtime construct, and as such the exception objects must be inspectable at runtime, but is there any standard or API for inspecting the exceptions? You catch the exception. Then you can examine it.Templas
@Dmitry why don't we just use functions with error callbacks and default to(if no callback is given) not blowing up our program each time an exception is thrown? Your are basically asking why C code is so brittle. Exceptions were designed to stop people forgetting to handle exception and thus blowing up the code in undiagnosable ways. Now it blows up showing you what went wrong and forcing you to handle the error condition (or if you don't handle it terminating the application in a controlled manner so your data is not corrupted).Templas
@LokiAstari that is true in Java where there are checked exceptions that compiler forces you to catch(often in places where most of the time you silence them so it would make more sense to use callbacks instead in these cases), and if C++ exceptions showed the whole stack of the exception in the case that your program blew up because of an exception in an external library whose name you do not know nor function throwing it you do not know. Often telling you silly things like "Please contact the application's support team for more information.", how do I know which module to replace?Decaliter
If you have not handled an error then the application MUST shutdown otherwise your application is left in an invalid state and is more than likely corrupting the data. Exception enforce this rule automatically; either the exception must be explicitly handled or the application shutdown automatically protecting your data. The problem with callbacks the way you describe is that the default action does nothing. This is basically leaving a time bomb in your code; an unhandeled error is broken code with no way to find the error.Templas
A
13

You could take a look here for a detailed explanation.

It may also help to take a look at a trick used in plain C to implement some basic sort of exception handling. This entails using setjmp() and longjmp() in the following manner: the former saves the stack in order to mark the exception handler (like "catch"), while the latter is used to "throw" a value. The "thrown" value is seen as if it has been returned from a called function. The "try block" ends when setjmp() is called again or when the function returns.

Antechoir answered 29/1, 2009 at 7:57 Comment(0)
U
11

I know this is an old question, but there's a very good exposition, explaining both the methods used in each of gcc and VC here: http://www.hexblog.com/wp-content/uploads/2012/06/Recon-2012-Skochinsky-Compiler-Internals.pdf

Unmarked answered 22/8, 2012 at 8:29 Comment(2)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.Consolata
@DonaldDuck I agree. But that link has so much detail I have no idea how to summarise it here.Unmarked

© 2022 - 2024 — McMap. All rights reserved.