Update: This is almost certainly a GCC bug. See part 2 for what the issue is and how to avoid it in your own code.

I recently came across a bug with GCC 4.0 in regard to exception handling. I found it while recompiling a C++ application in Xcode after upgrading my laptop from Mac OS X 10.3 to 10.4. OS X 10.4 uses GCC 4.0 by default, where 10.3 uses GCC 3.3 This application is not OS X specific, and uses only standard C++. I just use Xcode to develop on OS X. After performing the upgrade, one of my unit tests was failing. Score one for unit testing! As an aside, this is actually the second problem in 3rd party components my unit tests caught after upgrading a component. The other problem was a change in behavior in one of the Boost libraries. After some investigation, I was able to narrow the problem down to a very small application. Here's how to recreate it. While my instructions are for Xcode, the problem isn't Xcode specific, or even Apple specific (more on that later):

  1. Create a new project in Xcode
  2. Choose Command Line Utility, C++ Tool
  3. Enter the following code:
#include <iostream>
#include <vector>
#include <stdexcept>

int main (int argc, char * const argv[]) {
    try {
        std::vector<int> intVector;
        intVector.at(0);
    }
    catch (std::out_of_range &) {
        std::cout << "Caught std::out_of_range" << std::endl;
    }
    
    std::cout << "Finished normally" << std::endl;
    return 0;
}

The intVector.at(0) should throw an out of range exception since the vector is empty. This is why it is wise to always use at() instead of operator[]. operator[] doesn't do bounds checking and will silently give you garbage. However, running this program in Xcode results in the following output:

terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check

catch has exited due to signal 6 (SIGABRT).

Well, okay. That's odd. This means an uncaught exception of type std::out_of_range terminated the program. But I caught the exception! After some debugging, I determined the cause of this behavior stemmed from a new flag in GCC 4.0, -fvisibility=hidden, corresponding to the Xcode build setting of "Symbols Hidden by Default". If I turn this option off, I get the following output:

Caught std::out_of_range
Finished normally

catch has exited with status 0.

That's what should happen. So what's the deal? Well, Apple has a whole article on symbol visibility, which describes the rationale for this new option. And this option does make sense.... if your building a shared library. So here's the question: is this really a bug in GCC, or is it expected behavior? I think this is a bug. Granted, it doesn't make sense to turn on hidden visibility for an executable, but I see no reason why it should break perfectly valid code. And what if you want to catch std::out_of_range inside a shared library? There are certainly no warnings in the documentation about any side effects of this option. I also think this is a bug in Xcode. I can't think of any reason why this option should be enabled by default for a command line application. I can see turning on this option if I created a new dynamic library, but it doesn't seem to make sense for an application. I'm gonna file a bug with Apple and see what they say.

I also tried out this program with GCC 4.0 on a stock x86 Linux box. Sure enough, I got the same behavior: compiling with -fvisibility=hidden results in the exception not being caught. So this problem isn't specific to Apple's build of GCC or even PPC GCC. It's standard GCC 4.0 behavior. So I guess I should file a bug with the GCC team, as well. In any case, be wary of this new option in your applications after upgrading to GCC 4.0.