web-dev-qa-db-ja.com

未定義の動作を検出するC ++実装?

C++での膨大な数の操作により、未定義の動作が発生します。この場合、仕様はプログラムの動作がどうあるべきかについて完全にミュートされ、何でも起こります。このため、デバッグモードでコンパイルされるがリリースモードではないコード、一見無関係な変更が加えられるまで機能するコード、あるマシンでは機能するが別のマシンでは機能しないコードなど、さまざまなケースがあります。

私の質問は、C++コードの実行を調べ、プログラムが未定義の動作を呼び出すすべてのインスタンスにフラグを立てるユーティリティがあるかどうかです。 valgrindやチェック済みSTL実装などのツールがあるのは良いことですが、これらは私が考えているほど強力ではありません-たとえば、まだ割り当てているメモリを破棄し、STL実装をチェックすると、valgrindはフォールスネガティブになる可能性があります基本クラスポインタを介した削除をキャッチしません。

このツールは存在しますか?それとも、それを横に置いておくと便利でしょうか?

[〜#〜] edit [〜#〜]:一般に、C++プログラムが未定義の動作をする何かを実行する可能性があるかどうかを静的にチェックすることは決定不可能であることを認識しています。ただし、C++の特定の実行が未定義の動作を生成したかどうかを判断することは可能です。これを行う1つの方法は、仕様に定められた定義に従ってコードをステップスルーするC++インタープリターを作成し、各ポイントでコードの動作が未定義かどうかを判断することです。これは、特定のプログラムの実行で発生しない未定義の動作を検出しませんが、プログラムに実際に現れる未定義の動作を検出します。これは、一般的にまだ決定不可能であっても、TMが何らかの入力を受け入れるかどうかを決定することがチューリング認識可能である方法に関連しています。

ありがとう!

60
templatetypedef

John Regehr in デッドコードを見つけることで未定義の振る舞いのバグを見つける[〜#〜] stack [〜#〜] というツールを指摘しますサイトからの引用(emphasis mine):

最適化-不安定なコード(略して不安定なコード)は、ソフトウェアのバグの新しいクラスです:プログラムの未定義の動作のためにコンパイラの最適化によって予期せず排除されるコード。不安定なコードは、以下を含む多くのシステムに存在します。 LinuxカーネルとPostgresデータベースサーバー。不安定なコードの結果は、不適切な機能からセキュリティチェックの欠落まで多岐にわたります。

STACKはC/C++プログラムの不安定なコードを検出する静的チェッカーです。広く使用されているシステムへのSTACKの適用確認された160の新しいバグを発見しましたそして開発者によって修正されました。

C++ 11でも、constexpr変数と関数未定義の振る舞いコンパイル時にキャッチする必要があります

gcc ubsan :もあります。

GCCは最近(バージョン4.9)、CおよびC++言語のランタイムチェッカーであるUndefinedBehavior Sanitizer(ubsan)を取得しました。 ubsanを使用してプログラムをチェックするには、プログラムをコンパイルして、-fsanitize = undefinedオプションを使用してリンクします。このようなインストルメント化されたバイナリは実行する必要があります。 ubsanが問題を検出すると、「ランタイムエラー:」メッセージを出力し、ほとんどの場合、プログラムの実行を続行します。

および Clang Static Analyzer これには 多くのチェック 未定義の動作が含まれます。たとえば、clangs-fsanitize-fsanitize=undefinedを含むチェック:

-fsanitize = undefined:高速で互換性のある未定義の動作チェッカー。ランタイムコストが小さく、アドレス空間のレイアウトやABIに影響を与えない未定義の動作チェックを有効にします。これには、unsigned-integer-overflowを除く、以下にリストされているすべてのチェックが含まれます。

[〜#〜] c [〜#〜]については、彼の記事を見ることができます 未定義の振る舞いの悪用について真剣に考える時が来ました これは次のように述べています。

[..] GCCまたはLLVMを詰め込むのに必要な勇気を個人的に持っていないことを告白します利用可能な最良の動的未定義の振る舞いチェッカー:KCCおよびFrama-C。[...]

ここに kccへのリンク があり、引用します:

[...]未定義のプログラム(またはセマンティクスが欠落しているプログラム)を実行しようとすると、プログラムがスタックします。メッセージは、どこでスタックしたかを示し、その理由についてのヒントを提供する場合があります。出力の解読について、またはプログラムが未定義である理由の理解について支援が必要な場合は、.kdumpファイルをお送りください。[...]

Frama-Cへのリンク記事 ここでCインタープリターとしてのFrama-Cの最初の使用について説明し、 補遺 記事に。

16
Shafik Yaghmour

これは素晴らしい質問ですが、一般的に不可能(または少なくとも非常に難しい)であると私が考える理由について考えさせてください。

おそらく、そのような実装はほとんどC++ interpreterか、少なくともLISPやJavaのようなもののためのコンパイラでしょう。配列の外部で算術演算を実行したり、すでに解放されたものなどを逆参照したりしないように、ポインターごとに追加のデータを保持する必要があります。

ここで、次のコードについて考えてみます。

int *p = new int;
delete p;
int *q = new int;

if (p == q)
    *p = 17;

*p = 17は未定義の動作ですか?一方では、解放された後、pを逆参照します。一方、qの逆参照は問題なく、p == q.。

しかし、それは実際には重要ではありません。重要なのは、ifがtrueと評価されるかどうかは、実装ごとに異なる可能性があるヒープ実装の詳細に依存するということです。したがって、*p = 17を実際の未定義の動作に置き換えると、通常のコンパイラでは非常にうまく機能する可能性がありますが、架空の「UB検出器」では正常に実行されるプログラムがあります。 (典型的なC++実装はLIFOフリーリストを使用するため、ポインターが等しくなる可能性が高くなります。架空の「UB検出器」は、検出するためにガベージコレクション言語のように機能する可能性があります。解放後使用の問題。)

言い換えれば、単にimplementation-defined動作が存在するため、すべてのプログラムで機能する「UB検出器」を作成することは不可能だと思います。

そうは言っても、「超厳密なC++コンパイラ」を作成するプロジェクトは非常に興味深いものになるでしょう。開始したい場合はお知らせください。 :-)

20
Nemo

g++の使用

-Wall -Werror -pedantic-error

(できれば適切な-std引数も使用して)U.B。のかなりの数のケースを取り上げます。


-Wallがあなたにもたらすもの:

-衒学者
厳密なISOCおよびISOC++で要求されるすべての警告を発行します。禁止されている拡張機能を使用するすべてのプログラム、およびISOCおよびISOC++に準拠していないその他のプログラムを拒否します。 ISO Cの場合、使用される-stdオプションで指定されたISOC標準のバージョンに従います。

-Winit-self(C、C++、Objective-CおよびObjective-C++のみ)
それ自体で初期化される初期化されていない変数について警告します。このオプションは、-Wuninitializedオプションでのみ使用でき、-O1以降でのみ機能することに注意してください。

-Wuninitialized
自動変数が最初に初期化されずに使用された場合、または変数が「setjmp」呼び出しによって破壊される可能性がある場合に警告します。

printfおよびscanfファミリ関数の指定子を使用して実行できるさまざまな許可されていないこと。

11
dmckee

Clangには 一連のサニタイザー があり、さまざまな形式の未定義の動作をキャッチします。彼らの最終的な目標は、すべてのC++コア言語の未定義の動作をキャッチできるようにすることですが、未定義の動作のいくつかのトリッキーな形式のチェックが現在欠落しています。

まともな消毒剤のセットについては、試してみてください:

clang++ -fsanitize=undefined,address

-fsanitize=addressは(有効なメモリを指していない)不正なポインタの使用をチェックし、-fsanitize=undefinedは一連の軽量UBチェック(整数オーバーフロー、不正なシフト、ポインタの不整合など)を有効にします。

-fsanitize=memory(初期化されていないメモリ読み取りを検出するため)および-fsanitize=thread(データ競合を検出するため)も役立ちますが、これらのいずれも-fsanitize=addressと組み合わせたり、3つすべてにプログラムのアドレス空間への侵襲的な影響。

10
Richard Smith

SAFECode について読みたいと思うかもしれません。

これはイリノイ大学の研究プロジェクトであり、目標はフロントページ(上記のリンク)に記載されています。

SAFECodeプロジェクトの目的は、ガベージコレクションなしで、可能な場合は静的分析を使用し、必要に応じて実行時チェックを使用して、最小限の実行時チェックでプログラムの安全性を実現することです。 SAFECodeは、このプロジェクトで開発された積極的なコンパイラ技術を使用して、安全性の静的な実施を可能にするように設計された最小限のセマンティック制限でコード表現を定義します。

私にとって本当に興味深いのは、プログラムが静的に正しいことが証明できるときはいつでも、ランタイムチェックを排除することです。たとえば、次のようになります。

int array[N];
for (i = 0; i != N; ++i) { array[i] = 0; }

通常のバージョンよりもオーバーヘッドが発生しないはずです。

簡単に言えば、 Clang は、私が覚えている限り、未定義の動作についていくつかの保証がありますが、それを手に入れることはできません...

5
Matthieu M.

clangコンパイラは、いくつかの未定義の動作を検出し、それらに対して警告することができます。おそらくあなたが望むほど完全ではありませんが、それは間違いなく良いスタートです。

3
chmeee

残念ながら、私はそのようなツールを知りません。通常、UBは、コンパイラがすべての場合にUBを診断することが困難または不可能であるため、そのように定義されます。

実際、最良のツールはおそらくコンパイラの警告です。UBタイプの項目について警告することがよくあります(たとえば、基本クラスの非仮想デストラクタ、厳密なエイリアスルールの悪用など)。

コードレビューは、UBが信頼されているケースを見つけるのにも役立ちます。

次に、残りのケースをキャプチャするためにvalgrindに依存する必要があります。

3
Mark B

副次的な観察と同様に、計算可能性の理論によれば、すべての可能性のある未定義の動作を検出するプログラムを持つことはできません。

ヒューリスティックを使用し、特定のパターンに従う特定のケースを検出するツールのみを使用できます。または、場合によっては、プログラムが希望どおりに動作することを証明できます。ただし、一般的に未定義の動作を検出することはできません。

編集

プログラムが特定の入力で終了しない(ハングする、永久にループする)場合、その出力は未定義です。

この定義に同意する場合、プログラムが終了するかどうかを判断することは、決定不可能であることが証明されているよく知られた「停止問題」です。つまり、プログラムが存在しません(チューリングマシン、Cプログラム、C++プログラム、パスカルプログラム、一般的にこの問題を解決できる言語)。

簡単に言えば、任意のプログラムQと入力データIを入力として受け取り、Q(I)が終了した場合は出力TRUEとして出力し、Q(I)は終了しません。

詳細については、 http://en.wikipedia.org/wiki/Halting_problem を参照してください。

1
Giorgio

未定義の動作はndefinedです。他の人が示唆しているように、あなたができる最善のことは、標準に準拠することですが、それが何であるかわからないため、未定義のものをテストすることはできません。それが何であるかを知っていて、標準がそれを指定していれば、それは未定義ではありません。

ただし、何らかの理由で、実際には標準の記述に依存している場合はndefinedであり、特定の結果が得られる場合は、それを定義して、それを確認するための単体テストを作成することができます。特定のビルドに対して、それが定義されています。ただし、可能な限り未定義の動作を単純に回避することをお勧めします。

0
Arafangion