C++例外がCのコード中を通過するとクラッシュする件

woさんに教えていただいた件 (http://d.hatena.ne.jp/w_o/20051203#p3)、遅くなりましたが、現象確認しました。次のコードで再現しますね。

$ cat a.cpp
extern "C" {
  void b();
  void a() {
    throw 123;
  }
}

int main() {
  try {
    b();
  } catch(...) {}
}

$ cat b.c
void a();
void b() {
  a();
}

実行するとこのようにクラッシュします。

$ gcc -c b.c ; g++ a.cpp b.o ; ./a.out
terminate called after throwing an instance of 'int'
Aborted

ここで、Cのコードをwoさんの仰るように -funwind-tables 付きでコンパイルすると、クラッシュしなくなります。-fexceptions をつけた場合も大丈夫ですね。

$ gcc -fexceptions -c b.c ; g++ a.cpp b.o ; ./a.out
$ gcc -funwind-tables -c b.c ; g++ a.cpp b.o ; ./a.out
$

ところで、qsort(3)なんかは例外を通過させても問題が無いようです。次のサンプルコードはクラッシュしません。

 $ cat qsort-test.cpp
 #if 1
  #include <stdlib.h>
 #else
  #include <stddef.h>
  extern "C" void qsort(void *base, size_t nmemb, size_t size,
                       int(*compar)(const void *, const void *)) __attribute__((__nothrow__));
 #endif
 int cmp(const void*, const void*) {
   throw 123;
 } 
 
 int main() {
   char x[2] = {1, 0};
   try {
     qsort(x, 2, sizeof(char), cmp);
   } catch(int) {}
 }
 
 $ g++ qsort-test.cpp ; ./a.out
 $

glibc-2.3.2-200309260658のMakefileを見てみたところ、

 configure.in:  exceptions=-fexceptions
 Makeconfig: uses-callbacks = $(exceptions)
 stdlib/Makefile: CFLAGS-qsort.c = $(uses-callbacks)

となってまして、glibcのqsort.c はまず、-fexceptions 付きでコンパイルされています。また、/usr/include/stdlib.h を見ると、qsortのプロトタイプは

 extern void qsort (void *__base, size_t __nmemb, size_t __size,
                    __compar_fn_t __compar) __nonnull ((1, 4));

であり、他の標準C関数と異なり __THROW (つまり __attribute__((__nothrow__)) ) で修飾されていません。この2つのおかげですね。上のqsort-test.cで #if 0 を #if 1 に変更すると、abortするようになります。


ちなみに、C++規格(ISO/IEC 14882:2003 の §17.4.4.8/2, §25.4/4)を確認したら、「標準Cライブラリは例外を投げません。でも、qsort()とbsearch()だけは関数をコールバックするから投げるかも」と、明示的に特別扱いされていました。コールバックが例外を投げたとしても正常に動作しやがれとまでは書いていませんでしたが...glibcは大丈夫なように作ってあるわけですね。


また、余談ですが、誰も例外をcatchしなかった場合なんかに、

 terminate called after throwing an instance of 'int'
 Aborted

とメッセージを吐いてからabortしてくれるのは、GCC 3.4以降でdefaultとなったverbose terminate という機能のせいなんですね。3.3.xまでをお使いの方も、

 std::set_terminate(__gnu_cxx::__verbose_terminate_handler);

とすれば同様の状態にできるそうで。