So, you want to disable C++ exceptions. But maybe you're not sure what that actually does to your program? Welcome! But first, the most likely reason you came to this decision - you may have heard that exceptions in C++ are expensive.
So, much written and debated on the merits of exceptions. C++ defines exceptions at the language level, however compiler writers have added options to omit exception handling.
But what happens when you do this?
Let's give this little program a try.
#include <string>
#include <stdexcept>
#include <stdio>
void call_me_now()
{
int val = std::stoi("not a number");
printf("call_me_now value: %d\n", val);
}
void call_me()
{
struct foo {
foo() { printf("foo\n"); }
~foo() { printf("~foo\n"); }
} f;
call_me_now();
}
int main()
{
std::string s;
struct s_scope {
s_scope() { printf("s_scope\n"); }
~s_scope() { printf("~s_scope\n"); }
} s_scope_thing;
try
{
struct foo {
foo() { printf("boom\n"); }
~foo() { printf("~boom\n"); }
} f;
call_me();
printf(s.c_str());
}
catch (const std::bad_alloc & b)
{
printf("invalid_argument caught\n");
}
printf("leaving main \n");
}
This gets printed out, as you would expect.
s_scope
boom
foo
~foo
~boom
bad_alloc caught
leaving main
~s_scope
Now, let's try turning off exceptions. GCC describes how to do this in its manual under Exceptions, namely using -fno-exceptions
.
Unsurprisingly, we get a failure like this:
ex.cpp:36:42: error: exception handling disabled, use '-fexceptions' to enable
36 | catch (const std::invalid_argument & b)
What, wasn't that supposed to be changed to an empty macro? Nope - the keywords aren't changed, you need a double underscore. Do change try
and catch
accordingly and give it another go.
s_scope
boom
foo
terminate called after throwing an instance of 'std::invalid_argument'
what(): stoi
Aborted
So the exception becomes a direct call to abort
.
Clang describes the flag quite succintly here.
Again, we get an error when compiling.
ex.cpp:27:5: error: cannot use 'try' with exceptions disabled
try
^
And, again, a direct call to abort.
s_scope
boom
foo
terminate called after throwing an instance of 'std::invalid_argument'
what(): stoi
Aborted
The Microsoft Visual C++ compiler has its own flags to select an exception handling model, which also controls how Structured Exception Handling (SEH) behavior. That's a good post for some other day.
A simple cl ex.cpp
produces the following results:
ex.cpp(28): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc
If I recompile with the flag, I get the expected standard behavior. If I don't use the flag and run the program, however, I get the following behavior.
s_scope
boom
foo
invalid_argument caught
leaving main
~s_scope
What happened? Basically, unwind information is gone. Exceptions can still go through frames (you can see the invalid argument exception was caught), but the destructors for boom and foo never ran.
Why the difference? Well, all compilers seem to implement the exception handling behavior on a per-translation unit basis. MSVC simply removes the unwind information, but leaves enough machinery in place for catch
handlers to work.
MSVC links the standand library, which has exceptions turned on, and so those will indeed throw and either be dealt with internally or let the callers deal with whatever level of exception handling support they provide.
GCC's tricks are documented in the manual, but you can see how results may vary quite a bit depending on the settings of the translation units through which you are going.
Let's see what would happen with GCC if we link in some functions with exceptions enabled.
Let's add this myfn.cpp file.
#include <stdexcept>
static void ohno() {
throw std::runtime_error("oh no!");
}
static void blargh() {
struct blargh_scope {
blargh_scope() { printf("blargh\n"); }
~blargh_scope() { printf("~blargh\n"); }
} blargh_scope_thing;
ohno();
}
int fn1() {
try {
blargh();
} catch (const std::runtime_error& e) {
printf("caught %s\n", e.what());
}
return 1;
}
And change the following in ex.cpp
:
int fn1();
void call_me_now()
{
int val = fn1();
printf("call_me_now value: %d\n", val);
}
g++ -c myfn.cpp
g++ -c ex.cpp
g++ -o ex myfn.o ex.o
Now when we run, we get the following. The exception is thrown, unwindind takes place, the exception is handled, and the program continues.
s_scope
boom
foo
blargh
~blargh
caught oh no!
call_me_now value: 1
~foo
~boom
leaving main
~s_scope
If we let the exception escape, however, by removing the try/catch around the invocation to blargh
, we get the following output.
s_scope
boom
foo
blargh
terminate called after throwing an instance of 'std::runtime_error'
what(): oh no!
Note that not only everything outside goes unhandled, but we don't even get the ~blargh
destructor invocation.
Be really, really careful if you want to turn off exception handling in C++. You'll want to carefully control the compilation and usage of everything that goes into this.
Over time, C ABIs have proven quite stable, so if at all possible, I'm always a fan of having inter-library dependencies have plain C semantics. If you want a more sophisticated C++ interface over it, provide it as a header and let the caller decide what flags to use when compiling that, or have them tweak things for their environment.
Happy exception handling!