[解決済み] c++で例外はどのように動作するのか?
質問
例外処理は遅いと言われ続けていますが、その証拠を見たことがありません。そこで、「遅いかどうか」ではなく、「例外は裏でどう動いているのか」を聞いて、「いつ使うのか」「遅いかどうか」を判断できるようにしたいと思っています。
私が知っている限り、例外はreturnを何度も行うのと同じです。ただし、returnを行うたびに、次のreturnを行う必要があるか、停止する必要があるかをチェックします。リターンを停止するタイミングはどのように確認するのでしょうか。私は、例外の型とスタックの位置を保持する第2のスタックがあり、そこに到達するまでリターンを実行するのだと思います。この2つ目のスタックに触れるのはthrowとtry/catchのときだけだと思います。推測ですが、リターンコードで同様の動作を実装しても、同じ時間がかかると思います。しかし、これはすべて推測に過ぎないので、実際に何が起こるかを知りたいのです。
例外は実際にどのように動作するのでしょうか?
どのように解決するのですか?
推測する代わりに、C++ コードの小片とやや古い Linux インストールで、生成されたコードを実際に見てみることにしました。
class MyException
{
public:
MyException() { }
~MyException() { }
};
void my_throwing_function(bool throwit)
{
if (throwit)
throw MyException();
}
void another_function();
void log(unsigned count);
void my_catching_function()
{
log(0);
try
{
log(1);
another_function();
log(2);
}
catch (const MyException& e)
{
log(3);
}
log(4);
}
でコンパイルしました。
g++ -m32 -W -Wall -O3 -save-temps -c
でコンパイルし、生成されたアセンブリファイルを見てみました。
.file "foo.cpp"
.section .text._ZN11MyExceptionD1Ev,"axG",@progbits,_ZN11MyExceptionD1Ev,comdat
.align 2
.p2align 4,,15
.weak _ZN11MyExceptionD1Ev
.type _ZN11MyExceptionD1Ev, @function
_ZN11MyExceptionD1Ev:
.LFB7:
pushl %ebp
.LCFI0:
movl %esp, %ebp
.LCFI1:
popl %ebp
ret
.LFE7:
.size _ZN11MyExceptionD1Ev, .-_ZN11MyExceptionD1Ev
_ZN11MyExceptionD1Ev
は
MyException::~MyException()
であるため、コンパイラはデストラクタの非インラインコピーが必要であると判断しました。
.globl __gxx_personality_v0
.globl _Unwind_Resume
.text
.align 2
.p2align 4,,15
.globl _Z20my_catching_functionv
.type _Z20my_catching_functionv, @function
_Z20my_catching_functionv:
.LFB9:
pushl %ebp
.LCFI2:
movl %esp, %ebp
.LCFI3:
pushl %ebx
.LCFI4:
subl $20, %esp
.LCFI5:
movl $0, (%esp)
.LEHB0:
call _Z3logj
.LEHE0:
movl $1, (%esp)
.LEHB1:
call _Z3logj
call _Z16another_functionv
movl $2, (%esp)
call _Z3logj
.LEHE1:
.L5:
movl $4, (%esp)
.LEHB2:
call _Z3logj
addl $20, %esp
popl %ebx
popl %ebp
ret
.L12:
subl $1, %edx
movl %eax, %ebx
je .L16
.L14:
movl %ebx, (%esp)
call _Unwind_Resume
.LEHE2:
.L16:
.L6:
movl %eax, (%esp)
call __cxa_begin_catch
movl $3, (%esp)
.LEHB3:
call _Z3logj
.LEHE3:
call __cxa_end_catch
.p2align 4,,3
jmp .L5
.L11:
.L8:
movl %eax, %ebx
.p2align 4,,6
call __cxa_end_catch
.p2align 4,,6
jmp .L14
.LFE9:
.size _Z20my_catching_functionv, .-_Z20my_catching_functionv
.section .gcc_except_table,"a",@progbits
.align 4
.LLSDA9:
.byte 0xff
.byte 0x0
.uleb128 .LLSDATT9-.LLSDATTD9
.LLSDATTD9:
.byte 0x1
.uleb128 .LLSDACSE9-.LLSDACSB9
.LLSDACSB9:
.uleb128 .LEHB0-.LFB9
.uleb128 .LEHE0-.LEHB0
.uleb128 0x0
.uleb128 0x0
.uleb128 .LEHB1-.LFB9
.uleb128 .LEHE1-.LEHB1
.uleb128 .L12-.LFB9
.uleb128 0x1
.uleb128 .LEHB2-.LFB9
.uleb128 .LEHE2-.LEHB2
.uleb128 0x0
.uleb128 0x0
.uleb128 .LEHB3-.LFB9
.uleb128 .LEHE3-.LEHB3
.uleb128 .L11-.LFB9
.uleb128 0x0
.LLSDACSE9:
.byte 0x1
.byte 0x0
.align 4
.long _ZTI11MyException
.LLSDATT9:
驚きです。通常のコードパスには余分な命令は全くありません。代わりにコンパイラは、関数の末尾にあるテーブル (実際には実行ファイルの別のセクションに置かれます) を介して参照される、行外の修正コード ブロックを余分に生成しました。すべての作業は標準ライブラリが裏で行い、これらのテーブル (
_ZTI11MyException
は
typeinfo for MyException
).
OK、実はこれは私にとって驚きではなく、このコンパイラがどうやるかはすでに知っていたのです。アセンブリの出力の続きです。
.text
.align 2
.p2align 4,,15
.globl _Z20my_throwing_functionb
.type _Z20my_throwing_functionb, @function
_Z20my_throwing_functionb:
.LFB8:
pushl %ebp
.LCFI6:
movl %esp, %ebp
.LCFI7:
subl $24, %esp
.LCFI8:
cmpb $0, 8(%ebp)
jne .L21
leave
ret
.L21:
movl $1, (%esp)
call __cxa_allocate_exception
movl $_ZN11MyExceptionD1Ev, 8(%esp)
movl $_ZTI11MyException, 4(%esp)
movl %eax, (%esp)
call __cxa_throw
.LFE8:
.size _Z20my_throwing_functionb, .-_Z20my_throwing_functionb
ここに、例外を投げるためのコードがあります。例外が投げられるかもしれないという理由だけで余分なオーバーヘッドはありませんでしたが、実際に例外を投げたりキャッチしたりする際には明らかに多くのオーバーヘッドがあります。そのほとんどは
__cxa_throw
の中に隠されており、これは必ず
- その例外のハンドラを見つけるまで、例外テーブルの助けを借りてスタックを歩きます。
- そのハンドラに到達するまで、スタックを巻き戻します。
- 実際にハンドラを呼び出します。
単純に値を返す場合のコストと比較すると、例外は例外的な戻り値にのみ使用されるべき理由がわかるでしょう。
最後に、アセンブリファイルの残りの部分です。
.weak _ZTI11MyException
.section .rodata._ZTI11MyException,"aG",@progbits,_ZTI11MyException,comdat
.align 4
.type _ZTI11MyException, @object
.size _ZTI11MyException, 8
_ZTI11MyException:
.long _ZTVN10__cxxabiv117__class_type_infoE+8
.long _ZTS11MyException
.weak _ZTS11MyException
.section .rodata._ZTS11MyException,"aG",@progbits,_ZTS11MyException,comdat
.type _ZTS11MyException, @object
.size _ZTS11MyException, 14
_ZTS11MyException:
.string "11MyException"
typeinfoのデータです。
.section .eh_frame,"a",@progbits
.Lframe1:
.long .LECIE1-.LSCIE1
.LSCIE1:
.long 0x0
.byte 0x1
.string "zPL"
.uleb128 0x1
.sleb128 -4
.byte 0x8
.uleb128 0x6
.byte 0x0
.long __gxx_personality_v0
.byte 0x0
.byte 0xc
.uleb128 0x4
.uleb128 0x4
.byte 0x88
.uleb128 0x1
.align 4
.LECIE1:
.LSFDE3:
.long .LEFDE3-.LASFDE3
.LASFDE3:
.long .LASFDE3-.Lframe1
.long .LFB9
.long .LFE9-.LFB9
.uleb128 0x4
.long .LLSDA9
.byte 0x4
.long .LCFI2-.LFB9
.byte 0xe
.uleb128 0x8
.byte 0x85
.uleb128 0x2
.byte 0x4
.long .LCFI3-.LCFI2
.byte 0xd
.uleb128 0x5
.byte 0x4
.long .LCFI5-.LCFI3
.byte 0x83
.uleb128 0x3
.align 4
.LEFDE3:
.LSFDE5:
.long .LEFDE5-.LASFDE5
.LASFDE5:
.long .LASFDE5-.Lframe1
.long .LFB8
.long .LFE8-.LFB8
.uleb128 0x4
.long 0x0
.byte 0x4
.long .LCFI6-.LFB8
.byte 0xe
.uleb128 0x8
.byte 0x85
.uleb128 0x2
.byte 0x4
.long .LCFI7-.LCFI6
.byte 0xd
.uleb128 0x5
.align 4
.LEFDE5:
.ident "GCC: (GNU) 4.1.2 (Ubuntu 4.1.2-0ubuntu4)"
.section .note.GNU-stack,"",@progbits
さらに多くの例外処理テーブルと、分類された追加情報。
つまり、少なくとも Linux 上の GCC での結論は、例外が投げられるかどうかにかかわらず、コストは (ハンドラとテーブルのための) 余分なスペースであり、さらに例外が投げられたときにテーブルを解析しハンドラを実行する余分なコストとなります。もし、エラーコードの代わりに例外を使い、エラーが稀であれば、それは より速く というのも、エラーをテストするためのオーバーヘッドがもうないからです。
より詳細な情報が必要な場合、特に、すべての
__cxa_
関数が何をするのか、といった詳細な情報は、それらの元となった仕様書を参照してください。
関連
-
[解決済み】C++でユーザー入力を待つ【重複あり
-
[解決済み] error: 'ostream' does not name a type.
-
[解決済み】浮動小数点数の乱数生成
-
[解決済み] 複数の例外を1行でキャッチする(ブロックを除く)
-
[解決済み] callとapplyの違いは何ですか?
-
[解決済み] SQLiteのINSERT/per-secondのパフォーマンスを向上させる
-
[解決済み] 複数の例外を一度にキャッチする?
-
[解決済み] Javaにおける例外処理によるパフォーマンスへの影響とは?
-
[解決済み】C/C++の"-->"演算子とは何ですか?
-
[解決済み】ネストされたディレクトリを安全に作成するには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] テスト
-
[解決済み】C++エラー。アーキテクチャ x86_64 に対して未定義のシンボル
-
[解決済み】C++でint型に無限大を設定する
-
[解決済み】非静的メンバ関数への参照を呼び出す必要がある
-
[解決済み】C++でユーザー入力を待つ【重複あり
-
[解決済み】IntelliSense:オブジェクトに、メンバー関数と互換性のない型修飾子がある
-
[解決済み】cc1plus:エラー:g++で認識されないコマンドラインオプション"-std=c++11"
-
[解決済み] 式はクラス型を持つ必要があります。
-
[解決済み] [Solved] インクルードファイルが開けません。'stdio.h' - Visual Studio Community 2017 - C++ Error
-
[解決済み】標準ライブラリにstd::endlに相当するタブはあるか?