29

I can't find anything in the standard that forces functions declared with extern "C" to be noexcept, either implicitly or explicitly.

Yet, it should be clear that C calling conventions cannot support exceptions... or is it?

Does the standard mention this, somewhere that I've missed? If not, why not? Is it simply left as an implementation detail of sorts?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • can't imagine this scale of compatibility break in C++ language evolution, can we? – Sheen Jun 23 '14 at 09:38
  • 2
    It's pretty questionable as to whether or not it would break compatibility. Programs that leak exceptions from C functions may always have had undefined behaviour. – Puppy Jun 23 '14 at 09:39
  • 4
    Related http://stackoverflow.com/a/15845731/242520 – ta.speot.is Jun 23 '14 at 09:43
  • @ta.speot.is May be a dupe actually -.- – Lightness Races in Orbit Jun 23 '14 at 09:48
  • D&E mentions the scenario of a C function propagating an exception thrown in a C++ function. It might not be required to be supported, but it's probably explicitly specified *to allow* such support. Some functions are C language-linkage only because of name mangling (and implemented in C++). – dyp Jun 23 '14 at 09:49
  • 1
    The MSVC++ compiler appears to think it is unspecified, /EHs vs /EHsc. – Hans Passant Jun 23 '14 at 09:51
  • So you are asking why, within the `extern` specifier, the "C" linkage is not define as a special case for which every function is automatically declared `noexcept`? – Shoe Jun 23 '14 at 10:13
  • @Jefffrey: If you were to strip all the nuance out of the question, yes :P – Lightness Races in Orbit Jun 23 '14 at 10:19
  • For gcc, there is `-fexceptions`, which enables exception handling (also) *for C functions*. See https://gcc.gnu.org/onlinedocs/gcc-4.9.0/gcc/Code-Gen-Options.html#Code-Gen-Options and https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_exceptions.html "For GNU systems, all appropriate parts of the GNU C library are already compiled with `-fexceptions`" – dyp Jun 23 '14 at 10:21
  • I deleted ny answer, because I think now that you meant: "should the C++ standard allow a program to formally assume that a given C function behave correctly with regard to C++ exceptions, by allowing it to tag it `noexcept`?", like we would expect an asm hand coded imported function to follow the C calling conventions. – didierc Jun 23 '14 at 11:24

5 Answers5

22

As far as I can tell there is no guarantee that function defined with "C" linkage will not throw exceptions. The standard allows a C++ program both to call an external function with "C" language linkage, and to define functions written in C++ that have "C" language linkage. Therefore there is nothing to prevent a C++ program from calling a function with "C" language linkage that is actually written in C++ (in another compilation unit maybe, although even this is not necessary). It would be a strange thing to do, but it is hard to rule out. Also I don't see where in the standard it says that doing so would lead to undefined behaviour (in fact since the Standard cannot define the behaviour of function not written in C++, this would be the only usage where there is not formally undefined behaviour).

As a consequence I think it would be an error to assume that "C" linkage implies noexcept.

Marc van Leeuwen
  • 3,605
  • 23
  • 38
9

Um, I assume extern "C" just use C-linkage, not C function. It prevents the compiler from doing C++ name mangling.

More directly - Suppose this code.

// foo.cpp
extern "C" void foo()
{
    throw 1;
}

// bar.cpp
extern "C" void foo();
void bar()
{
    try
    {
        foo();
    }
    catch (int)
    {
        // yeah!
    }
}
ikh
  • 10,119
  • 1
  • 31
  • 70
  • 1
    Actually there's a lot more to it than just preventing name mangling. `extern "C++" void Foo();` is different than `extern "C" void Foo();`. They are different **types** and have different properties with linkage being only one of them. – Captain Obvlious Jun 24 '14 at 18:56
  • 1
    I thought it would affect calling convention too, and that exceptions required a particular calling convention for stack unwinding to work. – Lightness Races in Orbit Mar 29 '16 at 23:29
3

Marc van Leeuwen's answer is correct: looking up the current working draft, nothing seems to mandate that that functions declared extern "C" be implicitly noexcept. It is interesting to know that standard C++ prohibits that the C standard library functions within the C++ standard library from throwing. Those function are themselves usually specified as extern "C" (but this is implementation defined see 16.4.3.3-2). Take a look at clause 16.4.6.13 [Restriction on exception handling] and the accompanying footnotes 174 and 175.

Functions from the C standard library shall not throw exceptions [footnote 174] except when such a function calls a program-supplied function that throws an exception.[footnote 175]

Footnote 174:

  1. That is, the C library functions can all be treated as if they are marked noexcept. This allows implementations to make performance optimizations based on the absence of exceptions at runtime.

Footnote 175:

The functions qsort() and bsearch() ([alg.c.library]) meet this condition.

That being said, following the same strategy as the standard library is usually a good design guideline, and for reasons mention in Marc van Leeuwen's answer I think it's a good idea that user defined extern "C" functions be also specified with noexcept, unless it is handed a pointer to C++ function as callback argument like qsort and the likes. I made a small experiment with clang10, gcc10 with the following code:

#include <cstring>
#include <cstdlib>
#include <iostream>

extern "C" int cmp(const void* lhs, const void* rhs) noexcept;
extern "C" int non_throwing();

int main()
{
    constexpr int src[] = {10, 9, 8, 7, 6, 5};
    constexpr auto sz = sizeof *src;
    constexpr auto count = sizeof src / sz;

    int dest[count];
    int key = 7;

    std::cout << std::boolalpha
    // noexcept is unevaluated so no worries about UB here
        << "non_throwing: " << noexcept(non_throwing()) << '\n'
        << "memcpy: " << noexcept(std::memcpy(dest, src, sizeof dest)) << '\n'
        << "malloc: "<< noexcept(std::malloc(16u)) << '\n'
        << "free: " << noexcept(std::free(dest)) << '\n'
        << "exit: " << noexcept(std::exit(0)) << '\n'
        << "atexit: " << noexcept(std::atexit(nullptr)) << '\n'
        << "qsort: " << noexcept(std::qsort(dest, count, sz, cmp)) << '\n' // should fail
        << "bsearch: " << noexcept(std::bsearch(&key, dest, count, sz, cmp)) << '\n'; // should fail
}

The output for both gcc10 and clang10 was:

non_throwing: false
memcpy: true
malloc: true
free: true
exit: true
atexit: true
qsort: false
bsearch: false

For msvc142, if compiled with /EHsc then all outputs are obviously true. And with /EHs, all outputs are false, which makes the 'c' in /EHsc necessary for strict conformance.

0

There is nothing anywhere that says that extern "C" functions are noexcept. On the other hand, almost all C standard library functions are noexcept unless you do something strange. Typically, this boils down to invoking undefined behavior, but there are a few other cases. These should be all of them:

  • The function pointer argument to qsort() can throw; therefore qsort() can throw.
  • The same is true of bsearch().
  • You are allowed to replace malloc(), realloc(), and free(). If you do, these may throw.
  • In the previous case, calloc(), fopen(), fclose(), freopen(), system(), and strdup() may also throw. (strdup() is defined but not guaranteed to exist.)
  • setjmp() and catch(...) don't mix. At least one platform implemented longjmp() as the logical equivalent of throw jmp_buf, causing catch(...) to catch it.
  • Undefined behavior may throw. Some systems actually do implement *NULL as throw exception that can be caught by catch(...) even when compiling C code. If you execute undefined behavior anywhere, the entire program is undefined as soon as the code path is irrevocably committed to reaching undefined behavior, so this can cause C standard library functions to throw.
Joshua
  • 40,822
  • 8
  • 72
  • 132
  • Is behavior defined in case the comparison function passed to `qsort` or `bsearch` throws an exception? Some platforms' exception-handling mechanisms will only work if every stack frame between the `throw` and `catch` has a certain layout, but other layouts may be more efficient when stack unwinding isn't required, and C compilers for such platforms would generally have no reason not to use such layouts. – supercat May 23 '19 at 14:55
  • @supercat: Every arch I've seen with that property would blindly unwind any intermediate stack frames because the unwind proceeds down until it recognizes the next frame. This would probably cause trouble for `fopen()` but I can't imagine it would for `qsort()`. Furthermore, I'd expect that on an architecture for which this doesn't work that the function pointer to `qsort()` would be declared `noexcep` when included from C++. They already had to use a `#ifdef` to make the linkage work at all so it's not much more trouble. – Joshua May 23 '19 at 15:09
  • @supercat: On the other hand, if your assertion is true, than any function pointers passed to custom C functions must be `noexcept` thus making all C functions `noexcept` which is contrary to all answers to this question. – Joshua May 23 '19 at 15:12
  • The requirement wouldn't be that the functions be `noexcept`, but rather that they not throw/leak exceptions *in any circumstance where they are directly called from outside-language functions*. On x86, the simplest way for a function to access its arguments is to start with `push ebp / mov ebp,esp` and then on exit `mov esp,ebp / pop ebp`. If all functions to this, it will create a linked list of stack frames *but there's no requirement that all functions do that*. A function which receives one argument could start with `pop edx / pop eax / push eax / push edx`... – supercat May 23 '19 at 15:38
  • and then end with a simple `ret` if it will only need to use its argument once. Alternatively, it could keep track of relationship between the present and starting value of ESP, and use the `[ESP+(n+8)]` addressing mode if the stack is `n` bytes deeper than when the function started. Stack unwinding through functions which do such things may be impossible. – supercat May 23 '19 at 15:47
  • @supercat: Longjump can unwind right through that stuff. – Joshua May 23 '19 at 15:50
  • For an inner function to use `longjmp` to return to an outer function, it must be able to find a copy of the `jmp_buff` that the outer function created. There is no way to do that in thread-agnostic fashion without relying upon the cooperation of all intervening functions. Code that doesn't need to be thread-agnostic could store the address in a static-duration object, but it's usually desirable for freestanding implementations to generate thread-agnostic code whenever practical. – supercat May 23 '19 at 16:07
  • @supercat: Which is how catch originally worked, except that it was a per-thread variable (like errno). – Joshua May 23 '19 at 17:04
  • Many freestanding implementations are used to generate code for "raw metal" platforms that have no "operating system" outside the loaded program. Many such platforms can support threading if a program includes a small piece of machine code (often a dozen instructions or less) for context-switching, but any compiler functions that try to use static storage will break if invoked from more than one thread. Quality freestanding implementations designed for such purposes will thus avoid using static storage except in library functions (e.g. `rand()`) whose semantics would naturally require it. – supercat May 23 '19 at 17:12
0

A C function foo can call a function bar coded in C++, declared extern "C", such that bar is (perhaps indirectly) throw-ing some C++ exception.

A C function foo (perhaps called by some C++ function) can call longjmp whose runtime behavior is close to exception throwning.

IIRC, the first C++ compilers (Cfront) have generated C code using longjmp for translation of throw (and setjmp for translation of catch). Of course, C++ destructors complicate things.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547