104

リリース ビルドのパフォーマンスに影響を与えずにデバッグを容易にするために、C++ コードに多くのアサーションを追加する傾向があります。Nowassertは、C++ メカニズムを考慮せずに設計された純粋な C マクロです。

一方、C++ は を定義しますstd::logic_error。これは、プログラムのロジックにエラーがある場合にスローされることを意図しています (名前の由来)。インスタンスをスローすることは、完全で、より C++ っぽいassert.

問題はassertabort両方ともデストラクタを呼び出さずにすぐにプログラムを終了するため、クリーンアップをスキップすることですが、手動で例外をスローすると不要なランタイム コストが追加されます。これを回避する 1 つの方法は、独自のアサーション マクロを作成することです。このマクロSAFE_ASSERTは C 版と同じように機能しますが、失敗すると例外がスローされます。

この問題については、次の 3 つの意見を考えることができます。

  • C のアサートに固執します。プログラムはすぐに終了するため、変更が正しく展開されているかどうかは問題ではありません。また、#defineC++ で s を使用するのも同様に悪いことです。
  • 例外をスローし、 main() でキャッチします。コードがプログラムのどの状態でもデストラクタをスキップできるようにすることは悪い習慣であり、絶対に避ける必要があり、terminate() の呼び出しも同様です。例外がスローされた場合は、キャッチする必要があります。
  • 例外をスローして、プログラムを終了させます。 プログラムを終了させる例外は問題ありNDEBUGません。これは、リリース ビルドでは決して発生しません。キャッチは不要で、内部コードの実装の詳細を に公開しmain()ます。

この問題に対する決定的な答えはありますか? 専門的な参考文献はありますか?

編集済み:デストラクタをスキップすることは、もちろん、未定義の動作ではありません。

4

5 に答える 5

103
  • アサーションはデバッグ用です。出荷されたコードのユーザーは決してそれらを見るべきではありません。アサーションがヒットした場合は、コードを修正する必要があります。

    CWE-617: 到達可能なアサーション

この製品には、攻撃者によってトリガーされる可能性のある assert() または同様のステートメントが含まれており、アプリケーションの終了やその他の必要以上に深刻な動作につながります。

アサーションは論理エラーを検出し、より深刻な脆弱性状態に達する可能性を減らすのに適していますが、それでもサービス拒否につながる可能性があります.

たとえば、サーバーが複数の同時接続を処理し、1 つの接続で assert() が発生して他のすべての接続がドロップされる場合、これは到達可能なアサーションであり、サービス拒否につながります。

  • 例外は例外的な状況です。これが発生した場合、ユーザーは自分のやりたいことを実行できなくなりますが、別の場所で再開できる可能性があります。

  • エラー処理は、通常のプログラム フロー用です。たとえば、ユーザーに数値の入力を求めて解析不能なものを取得した場合、それは正常です。なぜなら、ユーザー入力は制御下にないため、考えられるすべての状況を常に当然のこととして処理する必要があるためです。(たとえば、有効な入力があるまでループし、間に「申し訳ありませんが、もう一度やり直してください。」)

于 2012-08-21T20:21:45.450 に答える
80

アサーションは、C++ コードでは完全に適切です。例外やその他のエラー処理メカニズムは、アサーションと同じことを意図したものではありません。

エラー処理は、エラーを回復したり、ユーザーに適切に報告したりする可能性がある場合に使用します。たとえば、入力ファイルを読み込もうとしてエラーが発生した場合、それについて何かしたいと思うかもしれません。エラーはバグが原因である可能性がありますが、単に特定の入力に対する適切な出力である可能性もあります。

アサーションは、API が通常はチェックされないときに API の要件が満たされていることをチェックしたり、開発者が構築によって保証されていると信じていることをチェックしたりするためのものです。たとえば、アルゴリズムが並べ替えられた入力を必要とする場合、通常はそれをチェックしませんが、デバッグ ビルドがその種のバグにフラグを立てるように、それをチェックするアサーションを持っている場合があります。アサーションは常に、正しく動作していないプログラムを示す必要があります。


クリーンでないシャットダウンが問題を引き起こす可能性のあるプログラムを作成している場合は、アサーションを避けたいと思うかもしれません。厳密に C++ 言語に関して未定義の動作は、ここではそのような問題とは見なされません。アサーションに到達することは、おそらく未定義の動作の結果であるか、または一部のクリーンアップが適切に機能するのを妨げる可能性のある他の要件に違反しているからです。

また、例外に関してアサーションを実装すると、アサーションの目的そのものと矛盾していても、キャッチされて「処理」される可能性があります。

于 2012-08-21T20:18:51.990 に答える
14

アサーションは、メソッドの実行前または実行後の内部状態など、内部実装の不変条件を検証するために使用できます。アサーションが失敗した場合、プログラムのロジックが壊れていることを意味し、これから回復することはできません。この場合、できる最善のことは、ユーザーに例外を渡さずに、できるだけ早く中断することです。(少なくとも Linux では) アサーションの本当に優れている点は、プロセスの終了の結果としてコア ダンプが生成されるため、スタック トレースと変数を簡単に調査できることです。これは、例外メッセージよりもロジック エラーを理解するのに役立ちます。

于 2012-08-21T20:35:44.897 に答える
13

abort() をアリングするためにデストラクタを実行しないことは、未定義の動作ではありません!

もしそうなら、それを呼び出すstd::terminate()のも未定義の動作になるので、それを提供する意味は何ですか?

assert()アサーションはエラー処理用ではなく、プログラムをただちに中止するためのものです。

于 2012-08-21T20:35:16.367 に答える
5

IMHO、アサーションは、違反した場合に他のすべてが無意味になる条件をチェックするためのものです。したがって、それらから回復することはできません。むしろ、回復は無関係です。

それらを 2 つのカテゴリにグループ化します。

  • 開発者の罪 (例: 負の値を返す確率関数):

float 確率() { return -1.0; }

アサート (確率 () >= 0.0)

  • マシンが壊れています (たとえば、プログラムを実行するマシンが非常に間違っています):

int x = 1;

アサート (x > 0);

これらはどちらも些細な例ですが、現実からかけ離れたものではありません。たとえば、ベクトルで使用するために負のインデックスを返す単純なアルゴリズムについて考えてみてください。または、カスタム ハードウェアに組み込まれたプログラム。というか、sh*t が起こるからです。

また、そのような開発ミスがあった場合、実装されている回復またはエラー処理メカニズムについて確信を持つべきではありません。同じことがハードウェア エラーにも当てはまります。

于 2014-12-16T18:37:57.027 に答える