8

Suppose I have the following two files, main.cpp:

#include <iostream>

class A {};    
void foo();

int main(void)
{
    try {
        foo();
    }
    catch(const A& e) {
        std::cout << "Caught an A." << std::endl;
    }
    return 0;
}

and foo.cpp:

class A {};
class B : public A {};

void foo()
{
    B b;
    throw b;
}

Now, when I compile each of these files separately, link the resulting object files, and run the resulting executable, I get the expected result:

$ clang++ --std=c++14 -c main.cpp
$ clang++ --std=c++14 -c foo.cpp
$ clang++ --std=c++14 main.o foo.o
$ ./a.out 
Caught an A.

And that boggles my mind! Class A has no virtual methods. Therefore, it is not polymorphic and its instances should carry no type information at runtime. The main.o object file is unaware of what is being thrown, since the actual throwing takes place inside foo(), whose body is defined in a separate compilation unit. The foo.o object file has more information, but is equally unaware of any catch statements and the expected types of caught exceptions.

In short: I do not see how the two source files compiled separately and then linked can produce the above input without having some runtime type information at disposal. Neither file compiled separately should have enough information to take the right catch block.

Witiko
  • 3,167
  • 3
  • 25
  • 43
  • 1
    B is subclass of A, so you can view on any instance of B with "lens" of A. – freestyle Nov 24 '16 at 22:34
  • why you are redefining `class A`? – Raindrop7 Nov 24 '16 at 22:36
  • Because of covariance – arturx64 Nov 24 '16 at 22:38
  • @Raindrop7 This behaves the same way as if it was declared in a .h file and then included in multiple files. – templatetypedef Nov 24 '16 at 22:38
  • @arturx64, @freestyle – I get why it works from the perspective of the language semantics. I am interested in how the compiler actually does it behind the scenes. Since both `foo.cpp` and `main.cpp` are compiled separately, neither should have enough information to take the right catch block. – Witiko Nov 24 '16 at 22:41
  • 1
    Hopelessly broad topic. In a nutshell: you are assuming that `throw b` throws an "object". Just a pointer. But are overlooking that it can also provide a *description* of the object. One that's compatible with the way `catch` checks for an object type match. – Hans Passant Nov 24 '16 at 22:55
  • 2
    It depends on implementation. For instance: msvc calls the CxxThrowException method on the "throw" statement. The second parameter of the function is _ThrowInfo. It's a struct holding various information about the type of exception that was thrown. – arturx64 Nov 24 '16 at 23:03
  • There are a few options for Exceptions implementation. Visual C++: Structured Exception Handling (SEH), C++ Exception Handling (EH). GCC : RTTI, SjLj exceptions, Zero-cost (table based) – arturx64 Nov 24 '16 at 23:15
  • @arturx64: Visual C++ implements table-based exception handling for x64 code as well. Plus, SEH is just mechanics the CRT and compiler use to transfer control. The actual object type lookup is implemented separately and unrelated to SEH. – IInspectable Nov 24 '16 at 23:18

2 Answers2

5

This is of course completely compiler dependent.

The constraints for all the compilers are:

  • the type of the exception is known when you throw (either at compile time, or at runtime in the case you'd throw a polymorphic object).
  • the applicable catch blocks (their can be several) and their types depend on the execution path.

This implies that the type must be recognized at runtime, even if the exception object is non-plymorphic.

An easy way to achieve this is to pass a pointer to a typeinfo object together with the thrown object itself. This is the approach used by GCC: see online code, here an extract of throwing in foo() for convenience:

    call    __cxa_allocate_exception
    mov     edx, 0
    mov     esi, OFFSET FLAT:typeinfo for B   ; <== !! 
    mov     rdi, rax
    call    __cxa_throw
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • I see. I failed to consider that just because the actual object doesn't carry any type information doesn't mean that they can't be statically determined at the site of throwing and passed along with the object. – Witiko Nov 24 '16 at 23:13
  • 2
    @Witiko It was a really interesting question. I generated code making `A` polymorphic, thinking that it would then use RTTI. But no: the compiler continues to pass its additional `typeinfo`. This is because the catching code can't know either if a polymorphic object or a non polymorphic object is thrown, and therefore a uniform interface is required. – Christophe Nov 24 '16 at 23:19
2

It's a combination of RTTI (Run-time type information) and the implementation specific encoded type data that is generated for comparing types when trying to assess which catch block gets what.

But simply comparing two types at face value might not yield the proper result (such as in your case with base and derived classes). In case of Windows & SEH, a special additional extended type information structure (etype_info or something like that) exists that contains all the classes in the hierarchy which need to be traversed to determine a potential match (as a base class can be a limiting view into the derived class). If after traversing a match is found, catch block is invoked.

Addendum

Exception handling requires special runtime support provided by the OS and therefore the way this happens is implementation defined (such as Structured Exception Handling in Windows) as long as the end result satisfies the standard.

Upstairs is the Windows-flavored gist of it.

  • How does the RTTI come into play, then, if the complete type information are passed along with the thrown object? – Witiko Nov 24 '16 at 23:13
  • It provides [type_info](http://en.cppreference.com/w/cpp/types/type_info) that is used in the comparison ops and is integrated into the exception information structure (`excpt_info` or something along those lines). Unwinding the stack while going back to the catch block, that structure is carried and used to test against the catch block type (whose type information is recorded in yet another structure recorded at compile time once it sees try-catch). – Benjamin Sisko Nov 24 '16 at 23:19
  • 1
    If you'd like to know more about the subject, you might want to explore one of the implementations like SEH, it is well documented on the web. RTTI in general as well. Hope this helps. – Benjamin Sisko Nov 24 '16 at 23:23
  • 1
    @BenjaminSisko: Reading up on SEH is of no use to answer this question. SEH are C exceptions. There are no classes or even class hierarchies in C. SEH is really just the tool used to transfer control. C++ exception handling is implemented on top of SEH in both the compiler as well as the CRT. Learning about SEH teaches you as much about C++ exception handling, as inspecting a road would give away hints about race car engines. – IInspectable Nov 24 '16 at 23:35
  • SEH is not "C exceptions". C++ exception handling is built on top of SEH, which means SEH contains the information pertinent to this specific question (exactly how it happens, which I provided) and is one avenue of investigation that may provide additional insights. – Benjamin Sisko Nov 24 '16 at 23:50
  • Also, your analogy is flawed. Which is hilarious as you literally explain why in the sentence before. – Benjamin Sisko Nov 24 '16 at 23:51
  • A race car engine, being C++ exception handling, can be built from more basic building blocks provided by different manufacturers (such as Microsoft's SEH). So inspecting of these building blocks can give you a insight which can be extended to others. – Benjamin Sisko Nov 24 '16 at 23:53
  • ... and I do find the information valuable. Welcome to StackOverflow, by the way. :-) – Witiko Nov 25 '16 at 00:06
  • SEH exceptions are commonly called *"C exceptions"*, and while C++ exceptions are built on top of SEH, it's completely opaque. SEH exceptions carry an [EXCEPTION_RECORD](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363082.aspx) structure, but the C++ specific information is hidden behind, what is essentially a `void*` (*ExceptionInformation*). So reading about SEH exception handling doesn't help you understand, how C++ exceptions are implemented. For example, it doesn't tell you one thing about stack unwinding. – IInspectable Dec 02 '16 at 20:58
  • Matt Pietrek's article [A Crash Course on the Depths of Win32™ Structured Exception Handling](https://www.microsoft.com/msj/0197/exception/exception.aspx), on the other hand, does explain, how Visual Studio implements C++ exceptions. At least for x86 architectures. – IInspectable Dec 02 '16 at 20:59