11

Lets say I have a shared library that is implemented in c++ but exposes a pure c interface. That library is then used in a c program.

Does gcc make any guarantees about what happens, if an exception escapes from the c++ library into the c application?
Will it e.g. always terminate the program?

I'm mainly interested in an answer for gcc on Linux on x64 and ARMv7-R, but answers about other operating systems, compilers and architectures are also welcome.

EDIT:
Just to make this clear: I'm not talking about letting exceptions pass through a c-function and then be caught in a calling c++ function or interaction with c or c++ callbacks. The application code itself is pure c. At some point it will call a function of the shared library (which internally is pure c++) and no application code will be called until that function returns. Also let's assume that I have no control over what flags are used to compile the application code.

MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • I _believe_ that such an exception will be treated as an uncaught exception (i.e. generates call to `std::terminate`), unless the C code is compiled with `-fexceptions`, in which case the exception can propagate (although may still eventually be uncaught). I don't have a verifiable source for this however. – davmac Feb 06 '17 at 11:33
  • This is one reason why, if mixing C and C++, a common guideline is that the `main()` is compiled as C++, not as C and that `extern "C"` functions catch exceptions and don't let them propagate to the caller. Either that, or the C++ code is compiled without exception support or doesn't throw at all. The propagation of a C++ exception through a C function is undefined. – Peter Feb 06 '17 at 11:47
  • @Peter: Yes, I'm not happy with the situation, but it is what it is. The library is used by c programs - I can't change that. If I forget to catch some exception at the boundary program termination is - in general - acceptable for me (In particular, I usually don't try to recover from a `bad_alloc`), but I don't want to risk undefined behavior. So the question I'm facing is if I should wrap the implementation of all interface functions into an additional `try { ...} catch(...){ std::terminate(); }` block to be on the safe side or if I can rely on the compiler to produce the same effect. – MikeMB Feb 06 '17 at 11:59
  • @Peter: As gcc afaik defines some things that are undefined in the c++ standard in order to stay compatible with C, I hoped this was one of them. – MikeMB Feb 06 '17 at 12:01
  • @Peter: Do you have a source for *"The propagation of a C++ exception through a C function is undefined"* for gcc (It obviously can't be defined in the c++ standard)? If so I'd be happy to accept this an answer to my question. – MikeMB Feb 06 '17 at 12:08
  • @MikeMB for what it's worth I know that there is some level of cross-language support for exceptions in GCC, and compiling C code with `-fexceptions` will allow exceptions to propagate through it by generating unwind tables. Logically, a C++ `noexcept` function (which has no unwind tables) and a C function (compiled without `-fexceptions`) should appear similar to the unwind routine, which is why I assume you would then get `std::terminate`. But again, I have no authoritative reference. – davmac Feb 06 '17 at 12:16
  • @MikeMB - gcc 6.3 manual says `-fexceptions` allows "C code to interoperate with exception handlers written in C++", but is unspecific on what "interoperate" means. I suspect it allows an exception from C++ to pass through C code, and be caught in C++ code. It's less clear if it also means `std::terminate()` will be called with no C++ caller of the C code, or if any C caller is built without `-fexceptions`. Say, if a comparer passed to `qsort()` throws, it's unclear - without checking how libc is built - if the exception will pass properly through `qsort()`. Too many unknowns to be sure, IMHO. – Peter Feb 06 '17 at 13:39
  • @CloseVoters: Although you are not obliged to do so, it would still be nice to tell me what exactly is unclear about my question, so I can improve it. – MikeMB Feb 06 '17 at 20:40
  • http://stackoverflow.com/questions/2101390/will-c-exceptions-safely-propagate-through-c-code – M.M Feb 06 '17 at 20:54
  • In MinGW-w64 you can choose various exception mechanisms , I believe dwarf2 does not propagate through C code but SJLJ does. There are pros and cons to each exception method. – M.M Feb 06 '17 at 20:55
  • @M.M: Thanks for the contribution, but the linked post asks about propagation through c code to a c++ exception handler. I thought I made it clear that this is not what I'm asking about. – MikeMB Feb 06 '17 at 21:04
  • You would probably get a better answer to this question on the `gcc-help@gcc.gnu.org` mailing list. – zwol Feb 16 '17 at 16:34

2 Answers2

1

From what I see about C++ exceptions, in this example which I took from MSDN, GCC seems to include the following assembly in the catch statement:

    call    __cxa_end_catch
    jmp     .L37
    movq    %rax, %rbx
    call    __cxa_end_catch
    movq    %rbx, %rax
    movq    %rax, %rdi
    call    _Unwind_Resume

Which makes use of what I can only assume are C++ library calls to functions that deal with exceptions (e.g. _Unwind_resume). So if the C code links against your library it will have to provide these symbols/functions which means that the code is going to be entering the C++ library to deal with the exceptions.

However, I don't yet know what the C++ library requires in order to do its job. I would expect it to be self contained but I'm not certain of it.

Edit: The answer to this question likely lies in the answers to the following two existing questions (and their interpretation):

  1. How is the C++ exception handling runtime implemented?
  2. How are exceptions implemented under the hood?
  3. How do exceptions work (behind the scenes) in c++

Edit 2: From this answer, it seems that since __cxa_throw uses a table for keeping track of available handlers. I would assume that when the table is exhausted, which in our case occurs when we enter C code, the function would call std::terminate. Hence, the C++ runtime (against which you must have linked) should take care of this for you without you needing to put up a catch all clause.

Since I'm still uncertain I will write up a test of this theory and update the answer with the results.

Community
  • 1
  • 1
nonsensickle
  • 4,438
  • 2
  • 34
  • 61
  • Any updates on this? I'm inclined to accept your answer but would feel better if it where backed - if not by official documentation - some tests. – MikeMB Jun 19 '17 at 11:16
1

If a C++ exception escapes from the outermost try block (which can only be within the outermost C++ function call), then the exception cannot be caught.

An uncaught exception always results in termination by std::terminate, according to the C++ standard, §15.3/9.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Can the compiler - when compiling the shared library - assume that any exception that escapes through and "extern C" function is uncaught? Because after that it "enters" a c program, which is not governed by the c++ standard so I'm a bit reluctant to accept this explanation – MikeMB Feb 20 '17 at 11:41
  • @MikeMB No, because the `extern "C"` function may be called by a C++ function. `gcc` (as opposed to `g++`) even emits empty exception-handling tables in C libraries, though I don't know whether or why they're necessary. – Potatoswatter Feb 20 '17 at 17:11
  • 1
    So if there is no exception related code generated in the main application (because it is an C-Application) and the code inside the shared library doesn't know whether the exception will be cached or not - how does the runtime environment determine if it should terminate the app or not? An answer that is purely based on the c++ standard is imho not enough when talking about cross-language behavior and things like shared libraries which are both outside of the scope of the c++ standard. – MikeMB Mar 06 '17 at 10:35