空前絶後のグダグダ感漂う、まあ、なんかなんだけど、続き。


さて、catchするほうは、大体理解できたので(いや、肝心の詰めの部分を諦めてしまってる感じはあるけど…)、投げるほうを考える。
と、言っても、今までの話で投げるほうは、大体わかってしまったも同然だろう。

/* throw.c */
#include <stddef.h>

extern struct type_info _ZTIPKc;

void *__cxa_allocate_exception( size_t size );
void __cxa_throw( void *obj,
		  struct type_info *tinfo,
		  void (*dest) (void*) ) __attribute__ ((__noreturn__));

void
throw_string( void )
{
  const char **ptr = __cxa_allocate_exception( sizeof(char*) );
  *ptr = "nanika";
  __cxa_throw( ptr, &_ZTIPKc, NULL );
}
// catch.cpp 
#include <stdio.h>
extern "C" void throw_string( void );

int
main()
{
  try {
    throw_string( );
  } catch ( const char *str ) {
    puts( str );
  }
  return 0;
}

これで、unwind-sjljな環境では、期待通り動作するはずだ。


…けど、なんか、これだけだと寂しいので、もうちょっとなんかしてみよう。
キャッチする/しない は、type_infoで判定してると、いうことで、type_infoを追いかけてみることにする。


けど、ここらへんの話は、実は、http://www.codesourcery.com/cxx-abi/abi.html#rttiに大体…とか、いや、そんな、昨日と同じネタを繰り返したりはしないですよ。


まず、type_infoは、型情報オブジェクトの基底クラスになってる。実際のtype_infoは次のどれかのインスタンスになる。

  • abi::__fundamental_type_info - int とか float とか
  • abi::__array_type_info
  • abi::__function_type_info
  • abi::__enum_type_info
  • abi::__class_type_info -
    • abi::__si_class_type_info - 多重継承しない (vtblが一個?)
    • abi::__vmi_class_type_info - vtblが複数あるの
  • abi::__pbase_type_info - なんかを指すポインタ
    • abi::__pointer_type_info - ただのポインタ(?)
    • abi::__pointer_to_member_type_info - メンバへのポインタ(?)


いや、ちょっとわからないのがあるけど…まあ、なんとなく理解できそうだ。んで、こいつらの実装はlibstdc++/libsupc++/tinfo.cc、libstdc++/libsupc++/tinfo2.ccにある。


大体理解できたところで、上のthrow.cで、_ZTIPKcに頼らないように書き換えてみる。
まず、ポインタ型でキャッチできるかどうかの判定は、tinfo2.ccのこれ。

bool __pbase_type_info::
__do_catch (const type_info *thr_type,
            void **thr_obj,
            unsigned outer) const
{
  if (*this == *thr_type)
    return true;      // same type
  if (typeid (*this) != typeid (*thr_type))
    return false;     // not both same kind of pointers
  
  if (!(outer & 1))
    // We're not the same and our outer pointers are not all const qualified
    // Therefore there must at least be a qualification conversion involved
    // But for that to be valid, our outer pointers must be const qualified.
    return false;
  
  const __pbase_type_info *thrown_type =
    static_cast <const __pbase_type_info *> (thr_type);
  
  if (thrown_type->__flags & ~__flags)
    // We're less qualified.
    return false;
  
  if (!(__flags & __const_mask))
    outer &= ~1;
  
  return __pointer_catch (thrown_type, thr_obj, outer);
}

outerが少し理解できないけど、一番肝心なのは多分、最初のところ。type_infoは、operator==がオーバーロードしてあって、

// We can't rely on common symbols being shared between shared objects.
bool std::type_info::
operator== (const std::type_info& arg) const
{
  return (&arg == this) || (__builtin_strcmp (name (), arg.name ()) == 0);
}

こんな感じ。よーするに、型情報の名前があってればいいらしい。


つまり、上のthrow.cは、次のように書き換えられる。

#include <stddef.h>
struct type_info;
struct pointer_type_info {
  /* type_info */
  void *vtbl;
  const char *name;

  /* pbase_type_info */
  unsigned int flags;
  void *pointee;
};

extern char _ZTVN10__cxxabiv119__pointer_type_infoE[]; /* pointer_type_infoのvtbl */

static const char pointer_to_constchar_name[] = "PKc"; /* const char へのポインタ */

struct pointer_type_info pointer_to_constchar = {
  (void*)&(_ZTVN10__cxxabiv119__pointer_type_infoE[8]), /* vtbl */
  pointer_to_constchar_name,                            /* name */
  0,                                                    /* flags */
  NULL,                                                 /* pointee */
};

void *__cxa_allocate_exception( size_t size );
void __cxa_throw( void *obj,
		  struct type_info *tinfo,
		  void (*dest) (void*) ) __attribute__ ((__noreturn__));

void
throw_string( void )
{
  const char **ptr = __cxa_allocate_exception( sizeof(char*) );
  *ptr = "nanika";
  __cxa_throw( ptr, (struct type_info*)&pointer_to_constchar, NULL );
}

また、__pbase_type_info::__do_catchは、名前が一致してなくても、constフラグなんかの整合性を見たあと、__pointer_catchを呼ぶようになっている。
__pointer_catchは、

inline bool __pbase_type_info::
__pointer_catch (const __pbase_type_info *thrown_type,
                 void **thr_obj,
                 unsigned outer) const
{
  return __pointee->__do_catch (thrown_type->__pointee, thr_obj, outer + 2);
}

こう。つまり、__pointeeメンバがdo_catchできればよいらしい。ので、

#include <stddef.h>
struct type_info;
struct pointer_type_info {
  /* type_info */
  void *vtbl;
  const char *name;

  /* pbase_type_info */
  unsigned int flags;
  void *pointee;
};

extern char _ZTVN10__cxxabiv119__pointer_type_infoE[];
extern char _ZTIc[]; /* char の type_info */

static const char pointer_to_constchar_name[] = "hogehoge"; /* 名前は一致しない */

struct pointer_type_info pointer_to_constchar = {
  (void*)&(_ZTVN10__cxxabiv119__pointer_type_infoE[8]),
  pointer_to_constchar_name,
  0,
  &_ZTIc, /* pointee が一緒 */
};

void *__cxa_allocate_exception( size_t size );
void __cxa_throw( void *obj,
		  struct type_info *tinfo,
		  void (*dest) (void*) ) __attribute__ ((__noreturn__));

void
throw_string( void )
{
  const char **ptr = __cxa_allocate_exception( sizeof(char*) );
  *ptr = "nanika";
  __cxa_throw( ptr, (struct type_info*)&pointer_to_constchar, NULL );
}

これでもいける。あとは、ユーザ定義型のtype_infoについても考える…とかやりたいと思ったけど、今日はやる気がこのへんなのでやめとく。


というわけで、これで、try-catchのシステムはほぼ完全に理解できた…はず。いや、完全ではないよ。けど、まあ、それなりに理解できただろう。
これで終わり、としてもいいんだけど、こっからが本番だよ。
何故、上のthrow.cはLinux環境だと動かないのか?いよいよ過去二回にわたり、僕の理解を拒んだunwind-dwarf2の実装に迫る。


いや、僕もまだ全然理解できてなくて、ひょっとすると、次回、「やっぱりわからなかったので終了」になってしまうかもしれないところだったりするんだけど。まあ、そうなったときはそうなったときで。