Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type resolution during exception handling

When an exception is thrown in C++ and the stack is unwound, how is is the correct handler (catch clause) picked to handle the exception?

void f1()
{
    throw 1;
}

void f2()
{
    try
    {
        f1();
    }
    catch(const char* e)
    {
        std::cout << "exc1";
    }
}

...
try
{
    f2();
}
catch(int& e)
{
    std::cout << "exc2";
}
...

For example this code unsurprisingly prints "exc2" because catch(int& e) is capable of handling the 1 int typed object.

What I don't understand is, how can this be resolved statically? Or is it resolved dynamically? Is type information propagated with the exception?

like image 958
imreal Avatar asked May 23 '17 17:05

imreal


People also ask

What are the types of exceptions 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.). C++ provides the following specialized keywords for this purpose: try: Represents a block of code that can throw an exception.

What data types can be thrown as exceptions?

One can throw exception of type int,float,long or custom data types like classes and structs. But which data type can't be thrown as exception in C++? Abstract types, (pointers to) incomplete types and types without accesible copy/move constructor.


2 Answers

As for most things, the C++ standard doesn't specify an implementation, but constrains valid implementations. There won't be a universal answer that knows about the specifics.

The Itanium ABI is a popular ABI that provides language-agnostic exception support. In that implementation, the unwinding API invokes the personality function of the stack frame, which receives the exception context, an exception structure and a reference to the exception handling table that guides catch behavior. The personality function is looked up based on the call's return address in the program's exception tables; it is assumed that the only instructions that can initiate an exception are call instructions. (GCC has extensions that allow to throw from signal handlers by enabling "non-call exceptions".) Compilers that use the Itanium ABI provide a personality function that knows how to check the runtime type of exception objects, and they compare that type to elements of the exception table.

There are other ways to do that. For instance, on 32-bit Windows, exception handling works by setting up handler functions, on the stack, as linked list items. These linked list nodes contain the address of exception handlers, and they use an EXCEPTION_RECORD to figure out where to go from there. I'm a bit sparse on the details myself, unfortunately.

The Itanium ABI, by the way, is not exclusive to C++. For instance, on Apple platforms, Objective-C code uses the Itanium ABI for exceptions as well. This has the interesting property that a catch-all clause in either language will catch exceptions from the other language.

like image 159
zneak Avatar answered Oct 18 '22 12:10

zneak


You could say that "the type information is propagated with the exception", but technically that's not true. When you throw something, that object gets copied/moved and the compiler will look for a appropriate catch clause in the order that they appear, where the object will get copied/moved to:

The exception is a match if any of the following is true:

  • E and T are the same type (ignoring top-level cv-qualifiers on T)

  • T is an lvalue-reference to (possibly cv-qualified) E

  • T is an unambiguous public base class of E

  • T is a reference to an unambiguous public base class of E

  • T is (possibly cv-qualified) U or const U& (since C++14), and U is a pointer or pointer to member (since C++17) type, and E is also a pointer or pointer to member (since C++17) type that is implicitly convertible to U by one or more of

    • a standard pointer conversion other than one to a private, protected, or ambiguous base class
    • a qualification conversion
    • a function pointer conversion (since C++17)
    • T is a pointer or a pointer to member or a reference to a const pointer (since C++14), while E is std::nullptr_t.

Additionally, if there is a catch-all clause (catch (...)), then it is taken if any of the above points do not apply. If no catch clause is suitable, the exception continues its way up the call stack.

like image 33
Rakete1111 Avatar answered Oct 18 '22 12:10

Rakete1111