web-dev-qa-db-ja.com

C ++での例外の呼び出しスタック

今日、私のC++マルチプラットフォームコードでは、すべての関数を試行錯誤しています。すべてのcatchブロックで、現在の関数の名前を例外に追加して再度スローします。これにより、最上位のcatchブロック(最後に例外の詳細を出力します)に完全な呼び出しスタックがあり、例外の原因を追跡するのに役立ちます。

それは良い習慣ですか、それとも例外のコールスタックを取得するより良い方法がありますか?

31
Igor Oks

いいえ、それは非常に恐ろしいことであり、例外自体にコールスタックが必要な理由はわかりません。例外の理由、行番号、最初の例外が発生したコードのファイル名は十分にわかります。

そうは言っても、スタックトレースが本当に必要な場合は、例外スローサイトでコールスタック情報を一度に生成するだけです。これを行うための単一のポータブルな方法はありませんが、VC++用の http://stacktrace.sourceforge.net/ と同様のライブラリを組み合わせて使用​​することはそれほど難しくありません。

24
anon

あなたがやっていることは良い習慣ではありません。理由は次のとおりです。

1.不要です。
デバッグ情報が生成されるようにプロジェクトをデバッグモードでコンパイルすると、GDBなどのデバッガーでの例外処理のバックトレースを簡単に取得できます。

2.面倒です。
これは、すべての機能に追加することを覚えておく必要があるものです。関数を見逃した場合、特にそれが例外を引き起こした関数である場合、それは大きな混乱を引き起こす可能性があります。そして、あなたのコードを見る誰もがあなたが何をしているかを理解しなければなりません。また、__ FUNC__または__FUNCTION__または__PRETTY_FUNCTION__のようなものを使用したに違いありません。残念ながら、これらはすべて非標準です(C++には、関数の名前を取得する標準の方法はありません)。

3.遅いです。
C++での例外の伝播はすでにかなり遅く、このロジックを追加してもコードパスが遅くなるだけです。キャッチと再スローにマクロを使用している場合、これは問題ではありません。コードのリリースバージョンでキャッチと再スローを簡単に省略できます。そうしないと、パフォーマンスが問題になる可能性があります。

いい練習
スタックトレースを作成するためにすべての関数をキャッチして再スローすることは良い習慣ではないかもしれませんが、例外が最初に発生したファイル名、行番号、関数名を添付することは良い習慣です投げた。 BOOST_THROW_EXCEPTIONでboost :: exceptionを使用すると、この動作が無料で得られます。また、例外のデバッグと処理に役立つ説明情報を例外に添付するとよいでしょう。とは言うものの、これはすべて例外が構築されたときに発生するはずです。いったん構築されると、ハンドラーへの伝播を許可する必要があります。厳密に必要以上に繰り返しキャッチして再スローすることはできません。重要な情報を添付するために特定の関数をキャッチして再スローする必要がある場合は問題ありませんが、すべての関数ですべての例外をキャッチし、すでに利用可能な情報を添付する目的では多すぎます。

22

より優雅な1つの解決策は、トレーサーマクロ/クラスを構築することです。したがって、各関数の上部に次のように記述します。

TRACE()

マクロは次のようになります。

Tracer t(__FUNCTION__);

クラスTracerは、構築時に関数名をグローバルスタックに追加し、破壊されるとそれ自体を削除します。その後、そのスタックは常にロギングまたはデバッグに使用でき、メンテナンスははるかに簡単(1行)で、例外のオーバーヘッドは発生しません。

実装の例には http://www.drdobbs.com/18440527http://www.codeproject.com/KB/cpp/cmtrace.aspx 、および http://www.codeguru.com/cpp/vs/debug/tracing/article.php/c4429 。また、次のようなLinux関数 http://www.linuxjournal.com/article/6391 は、このスタックオーバーフローの質問で説明されているように、よりネイティブに行うことができます: gcc C++アプリがクラッシュします 。 ACEのACE_Stack_Traceも一見の価値があります。

いずれにしても、例外処理方法は粗雑で柔軟性がなく、計算コストがかかります。クラス構築/マクロソリューションははるかに高速で、必要に応じてリリースビルド用にコンパイルできます。

7
Scott Stafford

すべての問題の答えは、通常は http://www.gnu.org/software/gdb/ (Linuxの場合)またはWindowsのVisual Studioの場合、優れたデバッガーです。プログラムのどの時点でも、必要に応じてスタックトレースを提供できます。

現在の方法は、実際のパフォーマンスとメンテナンスの頭痛の種です。デバッガーは、目標を達成するために発明されましたが、オーバーヘッドはありません。

2
Scott Stafford

これを見てください SO質問 。これはあなたが探しているものに近いかもしれません。クロスプラットフォームではありませんが、答えはgccとVisual Studioのソリューションを提供します。

1
zooropa

かなりのスタックトレースを提供する素敵な小さなプロジェクトがあります。

https://github.com/bombela/backward-cpp

0
Scott Stafford

処理されない例外は、呼び出し元の関数が処理するために残されます。これは、例外が処理されるまで続きます。これは、関数呼び出しの前後のtry/catchの有無にかかわらず発生します。つまり、tryブロックにない関数が呼び出された場合、その関数で発生した例外は自動的に呼び出しスタックに渡されます。したがって、必要なのは、最上位の関数をtryブロックに配置し、catchブロックで例外 "..."を処理することだけです。その例外はすべての例外をキャッチします。したがって、最上位の関数は次のようになります

int main()
{
  try
  {
    top_most_func()
  }
  catch(...)
  {
    // handle all exceptions here
  }
}

特定の例外に対して特定のコードブロックを設定する場合は、それも可能です。それらが「...」例外キャッチブロックの前に発生することを確認してください。

0
zooropa

スタックトレースサポートのためのもう1つのプロジェクト: ex_diag 。マクロはなく、クロスプラットフォームがあり、巨大なコードは必要ありません。ツールは高速で、明確で、使いやすいです。

ここでは、トレースする必要のあるラップオブジェクトのみが必要です。例外が発生した場合、オブジェクトはトレースされます。

0
Boris

ここの回答ではかなりの数の反論がなされていますが、この質問がC++ 11で尋ねられたので、デバッガーや面倒なロギングを必要とせずに、クロスプラットフォームの方法でニースのバックトレースを取得できるメソッドが追加されました。

std::nested_exception および std::throw_with_nested を使用します

StackOverflow here および here で説明されていますが、どのように例外のバックトレースを取得するかネストされた例外を再スローする適切な例外ハンドラーを記述するだけで、コード内でただし、トレースする関数にtry/catchステートメントを挿入する必要があります。

派生した例外クラスでこれを行うことができるため、そのようなバックトレースに多くの情報を追加できます!また、my Mit on GitHub またはmy "trace" library も確認できます。バックトレースは次のようになります。

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
0
GPMueller

Libcs​​dbgライブラリとのリンク(元の回答については https://stackoverflow.com/a/18959030/364818 を参照)は、ソースコードやサードパーティのソースを変更せずにスタックトレースを取得する最もクリーンな方法のように見えますコード(STLなど)。

これは、コンパイラを使用して実際のスタックコレクションを計測します。これは、実際に実行したいことです。

私はそれを使用したことがなく、GPLは汚染されていますが、それは正しいアイデアのように見えます。

0
Mark Lakata