■
空前絶後のグダグダ感漂う、まあ、なんかなんだけど、続き。
さて、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の実装に迫る。
いや、僕もまだ全然理解できてなくて、ひょっとすると、次回、「やっぱりわからなかったので終了」になってしまうかもしれないところだったりするんだけど。まあ、そうなったときはそうなったときで。