
C ++の例外に関する以前の記事では、多くの暗い場所が残っていましたが、
理解できないままである主なもの-それにもかかわらずそれはどのように実行されますか
例外が発生したときの制御の移動?
SJLJのすべては明らかですが、この技術は実際には
いくつかの費用のかからない(例外なし)テーブルエンジンに取って代わられました。
しかし、これはどのようなメカニズムであり、どのように配置されているかは、カットの下で理解できます。
この記事は、 C ++ Siberiaでのスピーチの準備の過程で登場し、退屈さで知られる著者を除いて、他の誰かに役立つ可能性のある詳細が明らかになりました。
はじめに
それはすべて、setjmp / longjmp関数が使用するバッファーのサイズを調べるという単純な要望から始まりました。
- sizeof(jmp_buf)== 64バイト(MSVC 2013、win32)
- sizeof(jmp_buf)== 256バイト(MSVC 2013、x64)
- sizeof(jmp_buf)== 200バイト(GCC-4.8.4、Ubuntu 14.04 x64)
そして、これはレジスタの数( AMD x86-64 )とどのように一致していますか?

MMXとFP87レジスタが組み合わされています。
32ビットモードでは、32 + 128 + 80 + 12(eip、flags)= 252バイト
64ビットの場合-128 + 256 + 80 + 24(...)= 480バイト
何かが合わない。
ドキュメントを読む:
setjmpを呼び出すと、現在のスタック位置、不揮発性レジスタおよびフラグが保存されます。不揮発性レジスタとは何ですか? ドキュメントをもう一度お読みください。
関数を呼び出すとき、レジスタの一部の内容を維持する責任は呼び出し元にあり、これらはいわゆる揮発性レジスタです。 着信側は残りのレジスタの内容を処理し、これらは不揮発性レジスタです。それは何であり、なぜそれが必要なのでしょうか?
レジスタを揮発性と不揮発性に分割
関数呼び出しを最適化することです。 このような部門は長い間存在していました。
いずれにせよ、 68Kアーキテクチャ(1979年)では、8個の汎用レジスタのうち2個と7個のアドレスレジスタが揮発性と見なされ 、残りは着信側によって保護されていました。
88Kアーキテクチャ(1988)では、32個の汎用レジスタのうち12個が着信側+(スタックおよびフレームポインタ)によって保護されていました。
IBM S / 360 (1964)には、16個の汎用整数(32ビット)レジスタと4個の浮動小数点レジスタがありますが、ハードウェアスタックはありません。 関数を呼び出す前に、すべてのレジスタは特別な領域に保存されます。 再帰呼び出しの場合、OSからメモリを動的に要求する必要があります。 パラメーターは、パラメーター値へのポインターのリストへのポインターとして渡されます。 実際、11個のレジスタは不揮発性です。
レジスタの数が少ないアーキテクチャの場合、レジスタを保存しても問題はありません。 PDP-11 (1970)には、汎用レジスタが6つしかありません。 そして、パラメーターはスタックを介して渡されます。 実際、「C呼び出し規約」( cdecl )が登場し、C言語が結晶化したのはここです。
8086.彼なしではどこにいるのでしょうか。 プロセッサには8個の汎用レジスタがありますが、アーキテクチャ上の負担がないのはBXだけです。 関数の呼び出しにはいくつかの規則があり、それらはパラメーターの受け渡しに関連しており、すべてのレジスターは揮発性であると見なされていました。
IA-32に留まらず、再びx86-64に戻ります。この場合、各呼び出しの前に値を保存するにはレジスタが多すぎます。 本格的な機能の場合、何らかの方法で実行する必要がありますが、小さな「機能レット」の場合は無駄です。 妥協が必要です。
特定のレジスタがどのクラスに属するかは誰が判断できますか? コンパイラ自体、これはアーキテクチャと間接的な関係があります。 GCC開発者の1人が2003年に書いたものを以下に示します。
特定のレジスタを割り当てるカテゴリを決定するのは簡単ではありませんでした。 AMD64には15個の汎用レジスタがあり(pr。Lane:%rspはカウントせず、とにかく保存します)、命令で8個を使用する(いわゆる拡張レジスタ)には、サイズを大きくするREXプレフィックスが必要です。 さらに、%rax、%rdx、%rcx、%rsi、および%rdiレジスタは、一部のIA-32命令で暗黙的に使用されます。 これらのレジスタを揮発性にして、命令の使用に関する制限を回避することにしました。
したがって、%rbx、%rbp、および拡張レジスタのみを不揮発性にすることができます。 一連のテストにより、不揮発性レジスタが割り当てられたときに最小コードが得られることが示されました(%rbx、%rbp、%r12-%r15)。
最初は、揮発性の6つのSSEレジスタを作成する必要がありました。 ただし、これらのレジスタは128ビットであり、通常64ビットのみがデータの格納に使用されるため、呼び出し側用に保存するのは呼び出し側よりも高価です。
さまざまな実験が行われ、すべてのSSEレジスタがvolatileと宣言されたときに最もコンパクトで高速なコードが得られるという結論に達しました。
これは依然として当てはまります。21ページのAMD64 ABI仕様を参照してください。
同じABIがOS Xでサポートされています。
マイクロソフトはそれを異なるものと考え、その区分は次のとおりです。
- volatile:RAX、RCX、RDX、R8:R11、XMM0:XMM5、YMM0:YMM5
- 不揮発性:RSI、RDI、RBX、RBP、RSP、R12:R15、XMM6:XMM15、YMM6:YMM15
ケースが豊富なアーキテクチャについてはどうですか?
これは、32個の整数レジスタと浮動小数点レジスタがあるPowerPC用のOS X 64ビットコンパイラの現状です。
- 揮発性:GPR0、GPR2:GPR10、GPR12、FPR0:FPR13、 合計11 + 14
- 不揮発性:GPR1、GPR11(*)、GPR13:GPR31、FPR14:FPR31、 合計21 + 18
合計:レジスタを2つのクラスに分離すると、関数呼び出しの汎用的な最適化が実装されます。
- レジスタの一部は引数を渡すために使用され、これはスタックを介して作業するよりも高速です(さらにレジスタの一部は公式のニーズに費やされます)
- これらのレジスタの数は、統計と典型的なコードに関するアイデアに基づいてコンパイラ開発者によって決定されます
- 残りのレジスタの内容は必要な場合にのみ保存されるため、小さくて貪欲でない関数の場合は、何も保存しない場合があります
- 本格的な関数を呼び出すと、すべてのレジスターの内容が保存されますが、これはこの関数の本体が機能する時間と比較して何もありません
登録ウィンドウ
別のアプローチとして、この手法を使用するプロセッサは、 Berkeley RISCプロジェクト(1980..1984)から成長しています。
Intel i960 (1989)は、16個のローカルおよび16個の汎用汎用レジスタを備えた32ビットプロセッサです。 パラメータはグローバルレジスタを介して渡され、関数が呼び出されると、すべてのローカルレジスタが特別な命令によって保存されます。 実質的にすべてのローカルレジスタは不揮発性ですが、ハードウェアサポートが何らかの高速化をもたらすことを期待して、強制的に保存されます。 ただし、今では、これは1 キャッシュラインにすぎません。
AMD 29K (1988) -192 (sic!)レジスタを備えた32ビットプロセッサ
- 64個のグローバルおよび128個のローカル整数レジスタ
- ローカルレジスタは、スタックの最上部を形成し、RAMで継続され、スタックは、スタックの最上部(グローバルレジスタの1つ)からのオフセットでアクセスされます。
- 関数の入力パラメータはローカルレジスタを介して送信され、リターン-グローバルを介して
- また、16ワードに収まらないデータや、たとえばローカルアレイやこれを持つものなど、誰かがアドレスを必要とする可能性があるデータ用のメモリ内に実際のスタックがあります。
SPARC (1987)は、レジスタの数が異なる場合があります(名前のSはスケーラブルを意味します)
- 典型的なプロセッサには128個の汎用レジスタがあります
- そのうち32-8個のグローバルと24個のローカルのみが同時に表示され、ウィンドウを形成します
- ウィンドウは、8つの入力(引数)、8つのローカルおよび8つの出力(次の呼び出し用)で構成されます。
- ローカルレジスタは循環バッファを形成し、関数が呼び出されると、ウィンドウは16レジスタシフトされます。 この場合、呼び出された関数の8つの出力レジスタが入力になります。
- プッシュされると、レジスタがスタックに入ります
Itanium (2001)は、SPARCビジネスの後継者です。
- 合計128個の汎用整数汎用レジスター(64ビット)と同数のフローティング
- それらの32はグローバルと見なされます
- 96はローカルであり、レジスタスタックのトップを形成します
- プロセッサ自体がロードとアンロードを処理し、レジスタの無限のスタックの幻想を作成します(RSE、レジスタスタックエンジン)
- 関数が呼び出されると、特別な命令allocを使用してレジスタウィンドウが作成され、コンパイラはサイズを明示的に設定する必要があります
- ウィンドウはSPARCと同様に配置されますが、その部分のサイズは柔軟であり、コンパイラによっても設定されます。合計サイズは96レジスタ以下です。
- 一部は関数の入力パラメーター用で、8以下
- ローカルデータのローカル部分
- out-この関数によって呼び出される関数のパラメーター用で、8以下
- 関数から関数を呼び出すと、登録ウィンドウがシフトし、わずかな動きで部品が入ります
- 通常のスタックも存在し、コンパイラはローカルレジスタスタックに配置できなかったすべてをスタックに入れます
間違いなく、Itaniumはオーディエンス賞を受賞しました。このプロセッサがうまくいかなかったことは残念です。
関数呼び出し
したがって、このアーキテクチャの素晴らしさをすべて考慮して、関数の呼び出しについて次の結論を導き出すことができます。
はい、オプティマイザーの後、関数本体の内容は主ブロスに似ている場合がありますが、この命令またはその命令が必要な理由と変数の値を見つける方法が常に明確ではありません。
ただし、関数の子呼び出しの時点で、この騒々しいアクティビティはすべてフリーズします。
プロセッサアーキテクチャに関係なく、レジスタからの現在のデータは何らかの形で損失から保護されています。 レジスタウィンドウアーキテクチャの場合、これは自然に発生します。 その他の場合、揮発性レジスタはメモリに保存されますが、これが一時的な値であり、メモリ内に場所がない場合は、再計算する必要があります。 不揮発性レジスタは変更されないか、値が復元されます。
基になる関数で例外が発生し、ある関数のcatchブロックの1つに制御を移したいとします。 実行コンテキストを復元するために必要なすべての情報は、すでにスタックまたはレジスタにあります。
tryブロックは努力を必要としない場合があります。スタックにスペースを割り当ててそこに何かを保存する必要はありません。 すべての情報はすでに保存されています。 しかし今、問題はその情報をどこに投稿したかに関する情報をどのように保存するかです。
幸いなことに、この情報は静的であり、コンパイル時に決定されます。 コンパイラはこれらすべてをテーブルに収集するため、コストのかからないテーブルエンジンになります。
これがMSVC(x64)およびGCC(x64)コンパイラでどのように実装されているかを見てみましょう。
MSVC(x64)
MSVCは各機能のプロローグとエピローグを作成しますが、RSP値はそれらの間で同じままです。 誰かがallocaを使用するまで、RBPは通常の登録と見なされます。 実験のためにいくつかの重要な関数を取り、そのプロローグの重要な部分をデバッガーで見てみましょう。
000000013F440850 mov rax、rsp可能であれば、オプティマイザーは初期化命令でプロローグコードを希釈します。
000000013F440853 push rbp
000000013F440854プッシュRDI
000000013F440855プッシュr12
000000013F440857プッシュr14
000000013F440859プッシュr15
000000013F44085B lea rbp、[rax-0B8h]#初期化
000000013F440862 sub rsp、190h
000000013F440869 mov qword ptr [rbp + 20h]、0FFFFFFFFFFFFFFFEFE#初期化
000000013F440871 mov qword ptr [rax + 10h]、rbx
000000013F440875 mov qword ptr [rax + 18h]、rsi
000000013F440879 mov rax、qword ptr [__security_cookie(013F4C5020h)]#これから、関数本体
そして、不揮発性レジスタが元の状態に復元されるエピローグ。
000000013F4410C2 lea r11、[rsp + 190h]コンパイラは、.pdataセクションでスタックプロモーションの情報を収集します。 各関数にはRUNTIME_FUNCTION構造があり、そこからunwindテーブルへのリンクがあります 。 その内容は、-dump -unwindinfoオプションを指定したリンクユーティリティを使用して取り出すことができます。 同じ関数に対して、以下を見つけます。
000000013F4410CA mov rbx、qword ptr [r11 + 38h]
000000013F4410CE mov rsi、qword ptr [r11 + 40h]
000000013F4410D2 mov rsp、r11
000000013F4410D5ポップr15
000000013F4410D7ポップr14
000000013F4410D9ポップr12
000000013F4410DB pop rdi
000000013F4410DC pop rbp
000000013F4410DD ret
00001D70 00020880 0002110E 000946C0?Write_table_header ...Unwindコードに興味があります -例外が発生したときに実行する必要があるアクションが含まれています。
巻き戻しバージョン:1
フラグをほどく:EHANDLER UHANDLER
プロローグのサイズ:0x3A
コード数:11
コードをほどく:
29:SAVE_NONVOL、レジスタ= rsiオフセット= 0x1D0
25:SAVE_NONVOL、レジスタ= rbxオフセット= 0x1C8
19:ALLOC_LARGE、サイズ= 0x190
0B:PUSH_NONVOL、レジスタ= r15
09:PUSH_NONVOL、レジスタ= r14
07:PUSH_NONVOL、レジスタ= r12
05:PUSH_NONVOL、レジスタ= rdi
04:PUSH_NONVOL、レジスタ= rbp
ハンドラー:0006BFD0 __GSHandlerCheck_EH
EHハンドラーデータ:00087578
GS Unwindフラグ:UHandler
Cookieオフセット:00000188
- 行の先頭の数字は、説明されているアドレスの次の命令アドレスの機能の開始に対するシフトを意味します。 プロローグの途中で例外が発生した場合(これは非常に奇妙です)、行われた変更のみをロールバックできます。
- 次に、命令の種類が来ます。たとえば、ALLOC_LARGEは、一定量のメモリをスタックに割り当てることを意味します。SAVE_NONVOL-既に割り当てられたメモリにレジスタを保存します。PUSH_NONVOL-RSPを減らして、レジスタをスタックに保存します
- 指示は逆順で進み、エピローグのアクションを繰り返します
GCC(x64)
同様に、GCCによって作成された同じ関数のプロローグとエピローグを分析します。
プロローグ
.cfi_startprocエピローグ:
.cfi_personality 0x9b、DW.ref .__ gxx_personality_v0
.cfi_lsda 0x1b、.LLSDA11339
pushq%r15
.cfi_def_cfa_offset 16
.cfi_offset 15、-16
pushq%r14
.cfi_def_cfa_offset 24
.cfi_offset 14、-24
pushq%r13
.cfi_def_cfa_offset 32
.cfi_offset 13、-32
movq%rdi、%r13
pushq%r12
.cfi_def_cfa_offset 40
.cfi_offset 12、-40
pushq%rbp
.cfi_def_cfa_offset 48
.cfi_offset 6、-48
pushq%rbx
.cfi_def_cfa_offset 56
.cfi_offset 3、-56
subq $ 456、%rsp
.cfi_def_cfa_offset 512
addq $ 456、%rspCFIプレフィックスはコールフレーム情報を意味し、アセンブラにスタックプロモーションの追加情報を記録する方法を指示します。 この情報は.eh_frameセクションに収集されます。dwarfdumpユーティリティと-Fスイッチを使用すると、読み取り可能な形式で表示できます。
.cfi_remember_state
.cfi_def_cfa_offset 56
popq%rbx
.cfi_def_cfa_offset 48
popq%rbp
.cfi_def_cfa_offset 40
popq%r12
.cfi_def_cfa_offset 32
popq%r13
.cfi_def_cfa_offset 24
popq%r14
.cfi_def_cfa_offset 16
popq%r15
.cfi_def_cfa_offset 8
ret
#プロローグここに見えるもの:
〈0〉 〈0x00000e08:0x00000f4a〉 〈〉 〈fdeオフセット0x00000e00長さ:0x00000060〉 〈eh aug data len 0x0〉
0x00000e08:〈off cfa = 08(r7)〉 〈off r16 = -8(cfa)〉
0x00000e0a:〈off cfa = 16(r7)〉 〈off r15 = -16(cfa)〉 〈off r16 = -8(cfa)〉
0x00000e0c:〈off cfa = 24(r7)〉 〈off r14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000e0e:〈off cfa = 32(r7)〉 〈off r13 = -32(cfa)〉 〈off r14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000e10:〈off cfa = 40(r7)〉 〈off r12 = -40(cfa)〉 〈off r13 = -32(cfa)〉
〈off r14 = -24(cfa)〉 〈off r15 = -16(cfa)〉 〈off r16 = -8(cfa)〉
0x00000e11:〈off cfa = 48(r7)〉 〈off r6 = -48(cfa)〉
〈オフr12 = -40(cfa)〉 〈オフr13 = -32(cfa)〉
〈オフr14 = -24(cfa)〉 〈オフr15 = -16(cfa)〉
〈オフr16 = -8(cfa)〉
0x00000e12:〈off cfa = 56(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
体
0x00000e19:〈off cfa = 64(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000e51:〈off cfa = 56(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000e52:〈off cfa = 48(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000e53:〈off cfa = 40(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000e55:〈off cfa = 32(r7)〉 〈off r3 = -56(cfa)〉 〈off r6 = -48(cfa)
〉 〈オフr12 = -40(cfa)〉 〈オフr13 = -32(cfa)〉
〈オフr14 = -24(cfa)〉 〈オフr15 = -16(cfa)〉
〈オフr16 = -8(cfa)〉
0x00000e57:〈off cfa = 24(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000e59:〈off cfa = 16(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000e5b:〈off cfa = 08(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000e60:〈off cfa = 64(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈off r13 = -32(cfa)〉 〈off r14 = -24(cfa)〉 〈off r15 = -16(cfa)〉
〈オフr16 = -8(cfa)〉
0x00000f08:〈off cfa = 56(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000f09:〈off cfa = 48(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000f0a:〈off cfa = 40(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000f0c:〈off cfa = 32(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈off r13 = -32(cfa)〉 〈off r14 = -24(cfa)〉 〈off r15 = -16(cfa)〉
〈オフr16 = -8(cfa)〉
0x00000f0e:〈off cfa = 24(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000f10:〈off cfa = 16(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000f12:〈off cfa = 08(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
0x00000f18:〈off cfa = 64(r7)〉 〈off r3 = -56(cfa)〉
〈オフr6 = -48(cfa)〉 〈オフr12 = -40(cfa)〉
〈オフr13 = -32(cfa)〉 〈オフr14 = -24(cfa)〉
〈オフr15 = -16(cfa)〉 〈オフr16 = -8(cfa)〉
- 最初の番号は命令のアドレスです
- 各アドレスについて、エントリが対応する間隔を見つけることができます
- レコードは、レジスタがフレームポインターである記述子で構成されます<off cfa = 48(r7)>(r7は%rsp、 dwarfdump.confを参照)、
- また、レジスタ記述子のリスト、たとえば、<off r3 = -56(cfa)>は、%rbxレジスタがフレームポインタからオフセット-56に格納されることを意味します
- プロローグはアセンブリに似ており、%r16レジスタが追加されました。これは、コンパイラがその目的のいくつかに使用します
- エピローグの説明はありません。明らかに、コンパイラは、エピローグの実行時に例外がないと考えています。
- cfa値が単調に減少するコードの分岐がいくつかあります。 これが起こる理由は明らかではありません。おそらくコンパイラーは関数をインライン化し、一時データをスタックに配置し、すべてがレッドゾーンに収まるまでスタックのロールバックを保存します 。
合計
それで終わりました。 その過程で、魔法がなかったことが判明しました。 例外をキャッチした後に状態を復元できるようにするために、アクションは必要ありません;コードの自然な実行中にすべてが自動的に保存されます。
ここでは、状態を復元するために、コンパイラーからの少しの助けが必要ですが、すべてが非常に控えめで、飾り気がありません。
一般に、現代のコンパイラーによる例外処理は、最も難しい問題を大騒ぎせずに完全に「労働者と農民」の方法を使って冷静に解決できる方法の良い例です。 開発者の尊重。