では、ランタイムがどのように例外処理を行うのか知りたいですか?
がっかりする準備をしなさい。
そうではないからです。ObjCには例外処理ABIはなく、既に見つけたSPIのみがあります。また、Objective-C例外ABIが、実際にはABIを処理するC++例外とまったく同じであることも発見したことは間違いありません。そのために、いくつかのコードから始めましょう。
#include <Foundation/Foundation.h>
int main(int argc, char **argv) {
@try {
@throw [NSException exceptionWithName:@"ExceptionalCircumstances" reason:@"Drunk on power" userInfo:nil];
} @catch(...) {
NSLog(@"Catch");
} @finally {
NSLog(@"Finally");
}
}
clangを実行して-ObjC -O3
(そして嫌な量のデバッグ情報を取り除いて)これを取得します:
_main: ## @main
push rbp
mov rbp, rsp
push r14
push rbx
mov rdi, qword ptr [rip + L_OBJC_CLASSLIST_REFERENCES_$_]
mov rsi, qword ptr [rip + L_OBJC_SELECTOR_REFERENCES_]
lea rdx, qword ptr [rip + L__unnamed_cfstring_]
lea rcx, qword ptr [rip + L__unnamed_cfstring_2]
xor r8d, r8d
call qword ptr [rip + _objc_msgSend@GOTPCREL]
mov rdi, rax
call _objc_exception_throw
LBB0_2:
mov rdi, rax
call _objc_begin_catch
lea rdi, qword ptr [rip + L__unnamed_cfstring_4]
xor eax, eax
call _NSLog
call _objc_end_catch
xor ebx, ebx
LBB0_8:
lea rdi, qword ptr [rip + L__unnamed_cfstring_6]
xor eax, eax
call _NSLog
test bl, bl
jne LBB0_10
LBB0_11:
xor eax, eax
pop rbx
pop r14
pop rbp
ret
LBB0_5:
mov rbx, rax
call _objc_end_catch
jmp LBB0_7
LBB0_6:
mov rbx, rax
LBB0_7:
mov rdi, rbx
call _objc_begin_catch
mov bl, 1
jmp LBB0_8
LBB0_12:
mov r14, rax
test bl, bl
je LBB0_14
jmp LBB0_13
LBB0_10:
call _objc_exception_rethrow
jmp LBB0_11
LBB0_16: ## %.thread
mov r14, rax
LBB0_13:
call _objc_end_catch
LBB0_14:
mov rdi, r14
call __Unwind_Resume
LBB0_15:
call _objc_terminate
ObjC ++でコンパイルしても、何も変わりません。(まあ、それは完全に真実ではありません。最後はclangの個人的なルーチン_objc_terminate
へのジャンプに変わります)。___clang_call_terminate
とにかく、このコードは3つの重要なセクションに分けることができます。1つ目は_main
、の開始から開始までLBB0_2
、またはtryブロックが発生する場所です。露骨に例外をスローしてtry
ブロックでキャッチしているため、コンパイラーは先に進んでブランチを削除LBB0_2
し、catchハンドラーに直接移動しました。この時点で、Objective-C、より正確にはCoreFoundationが例外オブジェクトを設定し、libC++は必要な巻き戻しフェーズ中に例外ハンドラーの検索を開始しました。
コードの2番目の重要なブロックは、私たちとブロックが存在する場所LBB0_2
の終わりまでです。すべてが順調であるため、この下のすべてのコードは死んでいます(そして、うまくいけばリリースで削除されます)が、そうではなかったと想像してみましょう。 LBB0_11
catch
finally
3番目の部分は、例外をキャッチしないようにしたなどの愚かなことをLBB0_8
した場合に、コンパイラがNSLoginからジャンプを発行する場所です。代わりに、このハンドラーは、をLBB0_2
呼び出した後に少し反転し、ボールをドロップしたことをアンワインドハンドラーに通知し、別の場所でハンドラーの検索を続行するように、に移動します。もちろん、私たちはメインなので、他のハンドラーはなく、離れるときに呼び出されます。objc_begin_catch
ret
objc_exception_rethrow()
std::terminate
このようなものを手で書き出そうとすると、悪い時間を過ごすことになると言っても過言ではありません。すべての__cxa_*
およびObjCSPI関数は、信頼できない方法で例外オブジェクトをスローし、(悲観的には多くの)ハンドラーが非常にタイトな順序で発行され、C++ABIコントラクトが確実に満たされるようにします。std::terminate
呼ばれる。アクティブなリスニングの役割を引き受けたい場合は、独自の関数を使用して例外処理を再定義することができます。Objective-Cにはobjc_setUncaughtExceptionHandler
、がありobjc_setExceptionMatcher
objc_setExceptionPreprocessor
ます。