C ++例外処理ランタイムはどのように実装されていますか?


84

C ++の例外処理メカニズムがどのように機能するかに興味があります。具体的には、例外オブジェクトはどこに保存され、キャッチされるまで複数のスコープをどのように伝播しますか?グローバルエリアに保管されていますか?

これはコンパイラ固有である可能性があるため、誰かがg ++コンパイラスイートのコンテキストでこれを説明できますか?


4
この記事を読むと役に立ちます
Ahmed Said

わかりませんが、C ++仕様には明確な定義があると思います。(私は間違っているかもしれませんが)
Paul Nathan

2
いいえ、仕様には定義がありません。実装ではなく、動作を決定します。ポールは、あなたが興味を持っているどの実装を指定したい場合があります。
ロブ・ケネディ


回答:


49

実装は異なる場合がありますが、要件に続くいくつかの基本的な考え方があります。

例外オブジェクト自体は、1つの関数で作成され、その呼び出し元で破棄されたオブジェクトです。したがって、通常、スタック上にオブジェクトを作成することは不可能です。一方、多くの例外オブジェクトはそれほど大きくありません。エルゴ、たとえば32バイトのバッファを作成し、より大きな例外オブジェクトが実際に必要な場合は、オーバーフローしてヒープにすることができます。

実際の支配権の移転に関しては、2つの戦略が存在します。1つは、スタックを巻き戻すのに十分な情報をスタック自体に記録することです。これは基本的に、実行するデストラクタと、例外をキャッチする可能性のある例外ハンドラのリストです。例外が発生した場合は、一致するキャッチが見つかるまで、これらのデストラクタを実行してスタックを実行し直します。

2番目の戦略では、この情報をスタック外のテーブルに移動します。これで、例外が発生すると、コールスタックを使用して、入力されたが終了されなかったスコープが検出されます。次に、それらは静的テーブルで検索され、スローされた例外が処理される場所と、その間に実行されるデストラクタが決定されます。これは、スタックの例外オーバーヘッドが少ないことを意味します。とにかくリターンアドレスが必要です。テーブルは追加のデータですが、コンパイラーはそれらをプログラムのデマンドロードセグメントに配置できます。


4
AFAIR g ++は、おそらくCとの互換性のために、2番目のアドレステーブルアプローチを使用します。MicrosoftC++コンパイラは、C ++例外がSEH(構造化例外処理)の上に構築されるため、組み合わせたアプローチを使用します。各C ++関数で、MSC ++はSEH例外処理レコードを作成して登録します。このレコードは、この特定の関数のtry-catchブロックとデストラクタのアドレス範囲を持つテーブルを指します。スローは、C ++例外をSEH例外としてパッケージ化し、RaiseException()を呼び出します。その後、SEHは制御をC ++固有のハンドラールーチンに戻します。
Anton Tykhyy 2009年

1
@Anton:はい、アドレステーブルアプローチを使用しています。詳細については、stackoverflow.com / questions / 307610 /…で別の質問に対する私の回答を参照してください。
CesarB 2009年

答えてくれてありがとう。Cの純粋主義者がC ++とその例外をどのように恐れるかがわかります。単純なtry / catchが実行時に無意識のうちに多数のスタックオブジェクトを作成したり、余分なテーブルでプログラムを肥大化させたりする可能性があるという考えが、組み込みシステムがそれらを回避することが多い理由です。
スピードプレーン2016年

@speedplane:いいえ、それは理解不足によるものです。エラー処理は決して無料ではありません。Cは、自分で書くように強制します。そして、めったに使用されないコードパスでfree()またはが欠落しているCプログラムの数は誰もが知っていfclose()ます。
MSalters 2016年

@MSalters私は同意しません、それはほとんど完全に理解の欠如です。エンジニアは、例外がどのように機能し、例外がコードにどのように影響するかを理解していないことがよくあります。これは、当然のことながら、例外を使用するときにためらいにつながります。例外処理の実装がより明確に伝達された場合(そして魔法のように見えなかった場合)、多くの人はそれらを使用することを躊躇しません。
スピードプレーン2016年

20

これは、15.1標準の例外をスローすることで定義されています。

スローは一時オブジェクトを作成します。
この一時オブジェクトのメモリがどのように割り当てられるかは指定されていません。

一時オブジェクトの作成後、制御は呼び出しスタック内の最も近いハンドラーに渡されます。スローポイントとキャッチポイントの間のスタックを巻き戻します。スタックが巻き戻されると、スタック変数は作成の逆の順序で破棄されます。

例外が再スローされない限り、一時はキャッチされたハンドラーの最後で破棄されます。

注:参照でキャッチすると、参照は一時オブジェクトを参照します。値でキャッチすると、一時オブジェクトが値にコピーされます(したがって、コピーコンストラクターが必要です)。

S.Meyersからのアドバイス(const参照によるキャッチ)。

try
{
    // do stuff
}
catch(MyException const& x)
{
}
catch(std::exception const& x)
{
}

3
未指定だ何か他のものである、プログラムがスタックを戻しし、どのように「一番近いハンドラは、」ここでプログラムが知っています。ボーランドは、それを実装する1つの方法について特許を取得していると確信しています。
ロブ・ケネディ

オブジェクトが作成の逆の順序で破棄される限り、コンパイラエンジニアでない限り、実装の詳細は重要ではありません。
マーティンヨーク

1
反対票を投じた:a)「S。マイヤーズ」ではなく「スコットマイヤーズ」。b)誤った引用: "Effective C ++": "Item 13:Catch exceptions byreference。 "。これにより、例外オブジェクトに情報を微調整/追加できます。
セバスチャンマッハ2011年

3
@phresnel:項目21:「可能な限りconstを使用する」を忘れないでください。例外を微調整する良いケースはありません。a)「修正と破棄」、b)再スロー、またはc)新しい例外を生成する必要があります。
マーティンヨーク

1
@phresnel:はい、あなたには理由があります(あなたの論理に同意しません)、私は私のものを持っています、そして私はこの特定の主題について彼らに話したり、実際に彼らの心を知っているとは主張しませんが(マイヤーズ、アレクサンドレスク、サッター)私は信じています私の解釈は有効です。ただし、シアトル地域にいる場合は、ノースウエストC ++ユーザーグループの定期的な参加者であるため、3人全員と話すことができます(マイヤーズは他の人よりも少ない頻度です)。
マーティンヨーク

13

あなたは見て可能性があり、ここで詳細な説明のため。

また、基本的な種類の例外処理を実装するためにプレーンCで使用されるトリックを確認することも役立つ場合があります。これには、setjmp()とlongjmp()を次のように使用する必要があります。前者は例外ハンドラー(「catch」など)をマークするためにスタックを保存し、後者は値を「スロー」するために使用されます。「スローされた」値は、呼び出された関数から返されたように見えます。「tryブロック」は、setjmp()が再度呼び出されたとき、または関数が戻ったときに終了します。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.