PVS-Studio静的コードアナライザーがメモリリークを検出できるかどうかをよく尋ねられます。 同様のテキストを何度も手紙に書かないために、ブログで詳細な回答をすることにしました。 はい。PVS-Studioはメモリリークやその他のリソースを検出できます。 このため、いくつかの診断がPVS-Studioに実装されており、実際のプロジェクトでエラーを検出する例が記事で示されます。
メモリとリソースのリークを特定する
メモリリークとは、時間内にメモリの不要なセクションを解放しないプログラムの実行エラーに関連する、空きRAMまたは仮想コンピュータメモリの量を無制限に削減するプロセスです。 CWEによると、メモリリークはCWE-401の欠陥として分類されます。
メモリリークは、 リソースリークの一種です。 別のタイプのリソースのリークの例は、ファイル記述子リークと呼ばれるエラーです。ファイルは開かれますが閉じられず、記述子はオペレーティングシステムに返されません。 CWEによると、このようなエラーはCWE-404として分類できます。
メモリまたは他のリソースのリークは、サービス拒否エラーにつながる可能性があります 。
メモリリークやその他のリソースを検出するには、 動的および静的コード分析ツールが使用されます。 これらのツールには、PVS-Studioアナライザーがあります。
問題のエラーのクラスを識別するために、PVS-Studioに次の診断が実装されています。
- V599 「Foo」クラスには仮想関数が含まれていますが、仮想デストラクタは存在しません。
- V680 「A、Bの削除」式は、「A」オブジェクトのみを破棄します。 次に、 '、'演算子は、式の右側から結果の値を返します。
- V689 「Foo」クラスのデストラクタは、仮想として宣言されていません。 スマートポインターがオブジェクトを正しく破棄しない可能性があります。
- V701 realloc()リークの可能性:realloc()がメモリの割り当てに失敗すると、元のポインターが失われます。 realloc()を一時ポインターに割り当てることを検討してください。
- V772 voidポインターに対して「削除」演算子を呼び出すと、未定義の動作が発生します。
- V773 関数は、ポインター/ハンドルを解放せずに終了しました。 メモリ/リソースリークの可能性があります。
- V779 到達不能コードが検出されました。 エラーが存在する可能性があります。
- V1002 ポインター、コンストラクター、およびデストラクターを含むクラスは、自動的に生成されたoperator =またはコピーコンストラクターによってコピーされます。
- V1005 リソースは「X」機能を使用して獲得されましたが、互換性のない「Y」機能を使用して解放されました。
例
オープンソースコードのメモリリークのPVS-Studioアナライザーによる検出の例を見てみましょう。
例N1
NetDefenderプロジェクト PVS-Studio警告:V773「m_pColumns」ポインターはデストラクタでリリースされませんでした。 メモリリークが発生する可能性があります。 fireview.cpp 95
2つのオブジェクトがコンストラクターで作成されることに注意してください。
- タイプCBrushのオブジェクトへのポインターは、 m_pBrush変数に格納されます。
- CStringList型のオブジェクトへのポインターは、 m_pColumns変数に格納されます。
CFireView::CFireView() : CFormView(CFireView::IDD) { m_pBrush = new CBrush; ASSERT(m_pBrush); m_clrBk = RGB(148, 210, 252); m_clrText = RGB(0, 0, 0); m_pBrush->CreateSolidBrush(m_clrBk); m_pColumns = new CStringList; ASSERT(m_pColumns); _rows = 1; start = TRUE; block = TRUE; allow = TRUE; ping = TRUE; m_style=StyleTile; }
デストラクタでは、1つのオブジェクトのみが破棄され、そのアドレスはm_pBrush変数に格納されます。
CFireView::~CFireView() { if(m_pBrush) { delete m_pBrush; } }
m_pColumns変数は明らかに忘れられていたようです。 その結果、メモリリークが発生します。
例N2
プロジェクトFar2l(FAR v2のLinuxポート)。 検討中のエラーは、2つの異なるPVS-Studio診断によってすぐに検出されるという点で興味深いものです。
- V779到達不能コードが検出されました。 エラーが存在する可能性があります。 7z.cpp 203
- V773「t」ポインターを解放せずに関数が終了しました。 メモリリークが発生する可能性があります。 7z.cpp 202
BOOL WINAPI _export SEVENZ_OpenArchive(const char *Name, int *Type) { Traverser *t = new Traverser(Name); if (!t->Valid()) { return FALSE; delete t; } delete s_selected_traverser; s_selected_traverser = t; return TRUE; }
returnステートメントとdelete ステートメントは混同されます。 その結果、 deleteステートメントは実行されません。 アナライザは、到達不能なコードメッセージとメモリリークメッセージを表示することにより、これについて警告します。
例N3。
Firebirdプロジェクト。 PVS-Studio警告:V701 realloc()リークの可能性:realloc()がメモリの割り当てに失敗すると、元のポインター 's-> base'が失われます。 realloc()を一時ポインターに割り当てることを検討してください。 mstring.c 42
int mputchar(struct mstring *s, int ch) { if (!s || !s->base) return ch; if (s->ptr == s->end) { int len = s->end - s->base; if ((s->base = realloc(s->base, len+len+TAIL))) { s->ptr = s->base + len; s->end = s->base + len+len+TAIL; } else { s->ptr = s->end = 0; return ch; } } *s->ptr++ = ch; return ch; }
問題の関数は、文字列に文字を追加することを目的としています。 文字列を格納するために使用されるバッファは、 realloc関数を呼び出すことにより増加します。 エラーは、 realloc関数がメモリバッファのサイズを増やすことができない場合、メモリリークが発生することです。 理由は、十分なサイズのメモリブロックがない場合、 realloc関数はNULLを返し、同時に前のメモリバッファを解放しないためです。 関数の結果はすぐにs->ベース変数に書き込まれるため、以前に割り当てられたブロックを解放する方法はありません。
エラーは、一時変数を追加してfree関数を呼び出すことで修正できます。
int mputchar(struct mstring *s, int ch) { if (!s || !s->base) return ch; if (s->ptr == s->end) { void *old = s->base; int len = s->end - s->base; if ((s->base = realloc(s->base, len+len+TAIL))) { s->ptr = s->base + len; s->end = s->base + len+len+TAIL; } else { free(old); s->ptr = s->end = 0; return ch; } } *s->ptr++ = ch; return ch; }
静的および動的分析
例としてPVS-Studioを使用すると、静的アナライザーがさまざまなタイプのリソースリークを検出できることがわかります。 ただし、公平を期すために、一般に、静的アナライザーは動的コードアナライザーのリーク検出の分野で負けていることに注意してください。
エラーを見つけるには、静的アナライザーがポインターの使用方法を追跡する必要があり、これは非常に難しいタスクです。 関数とアナライザーの間で巧妙にポインターを渡し、ソースコードを調べて、メモリリークが発生したかどうかを追跡することは困難です。 アナライザーはプログラムがどの入力データを処理するかを知らないため、これがまったく不可能な場合もあります。
動的アナライザーは、より多くのメモリーまたはリソースのリークを検出します。 彼らは何も追跡する必要はありません。 プログラム内でリソースが割り当てられている場所を覚えて、プログラムの終了前にリソースが解放されるかどうかを確認するだけです。 そうでない場合は、エラーが見つかりました。 したがって、動的アナライザーは、さまざまなタイプのリークをより正確かつ確実に検出します。
動的分析が静的分析よりも強力であることは、前述の説明からはまったくわかりません。 動的分析の方法論は、静的分析の方法論と同様に、長所と短所の両方があります。 特に、リークの分野では、ダイナミックアナライザーの方が強力です。 タイプミスや到達不能コードの検索など、その他の分野では、効果がなかったり、まったく役に立たなかったりします。
静的分析と動的分析を対比しないでください。 これらの手法は競合しませんが、互いに補完します。 コードの品質と信頼性を改善する問題を解決するときは、両方のタイプのツールを使用する必要があります。 私はこれについて何度も書きましたが、繰り返したくありません。 この問題をより詳細に理解したい人のために、いくつかのリンクを提供します:
- 静的および動的コード分析 。
- 静的解析に関する神話。 神話3動的解析は静的解析よりも優れています 。
- Valgrindは優れていますが、十分ではありません 。
- 静的アナライザーを使用して、動的アナライザーValgrindのコードをチェックします 。
おわりに
PVS-Studio静的コードアナライザーは、メモリリークやその他のリソースに関連するさまざまなエラーを検出できます。 定期的に使用して、コードの作成段階やプロジェクトの夜間ビルド中であってもエラーを修正します。
PVS-Studioチームは、トラブルのないコードを望んでいます。
この記事を英語圏の聴衆と共有したい場合は、翻訳へのリンクを使用してください:Andrey Karpov。 はい、PVS-Studioはメモリリークを検出できます