web-dev-qa-db-ja.com

コード内のどの部分が使用されていないかを知るにはどうすればよいですか?

未使用のコードを削除することになっているレガシーC++コードがあります。問題は、コードベースが大きいことです。

どのコードが呼び出されなかった/使用されなかったのかを知るにはどうすればよいですか?

309
user63898

未使用のコードには2つの種類があります。

  • ローカルなもの、つまり、一部の関数では一部のパスまたは変数が使用されていない(または使用されているが意味のない方法で、書き込まれているが読み取られていないなど)
  • グローバルなもの:呼び出されない関数、アクセスされないグローバルオブジェクト

最初の種類では、優れたコンパイラーが役立ちます。

  • -Wunused(GCC、 Clang )は未使用の変数について警告する必要があり、Clangの未使用のアナライザーは(使用されていても)読み取られない変数について警告するようにインクリメントされています。
  • -Wunreachable-code(古いGCC、 2010で削除 )アクセスされないローカルブロックについて警告する必要があります(早期のリターンまたは常にtrueと評価される条件で発生します)
  • コンパイラーは通常、例外がスローされないことを証明できないため、未使用のcatchブロックについて警告するオプションはありません。

第二に、それははるかに困難です。静的にはプログラム全体の分析が必要であり、リンク時間の最適化によりデッドコードが実際に削除される場合でも、実際にはプログラムは実行時に非常に変換されているため、ユーザーに意味のある情報を伝えることはほぼ不可能です。

したがって、2つのアプローチがあります。

  • 理論的なものは、静的アナライザーを使用することです。コード全体を一度に非常に詳細に検査し、すべてのフローパスを見つけるソフトウェア。実際には、ここで機能するものは知りません。
  • 実用的な方法は、ヒューリスティックを使用することです。コードカバレッジツールを使用します(GNUチェーンではgcovです。適切に機能するためには、コンパイル中に特定のフラグを渡す必要があります)。さまざまな入力(ユニットテストまたは非回帰テスト)の適切なセットでコードカバレッジツールを実行すると、デッドコードは必然的に未到達コード内にあるため、ここから開始できます。

このテーマに非常に興味があり、自分でツールを実際に動作させる時間と傾向がある場合は、Clangライブラリを使用してそのようなツールを構築することをお勧めします。

  1. Clangライブラリを使用してAST(抽象構文ツリー)を取得します
  2. エントリーポイント以降からマークアンドスイープ分析を実行する

Clangがコードを解析し、オーバーロード解決を実行するため、C++言語の規則に対処する必要がなく、手元の問題に集中することができます。

ただし、この種の手法では、使用できない仮想オーバーライドを特定することはできません。なぜなら、それらは推論できないサードパーティのコードによって呼び出される可能性があるからです。

195
Matthieu M.

未使用の関数全体(および未使用のグローバル変数)の場合、GCCとGNU ldを使用していれば、GCCは実際にほとんどの作業を実行できます。

ソースをコンパイルするときは-ffunction-sections-fdata-sectionsを使用し、リンクするときは-Wl,--gc-sections,--print-gc-sectionsを使用します。リンカは、呼び出されなかったために削除できるすべての関数と、参照されなかったすべてのグローバルをリストします。

(もちろん、--print-gc-sectionsの部分をスキップして、リンカーに関数をサイレントに削除させて、ソースに残しておくこともできます。)

注:これは未使用の完全な関数のみを検出し、関数内のデッドコードについては何もしません。ライブ関数のデッドコードから呼び出された関数も保持されます。

一部のC++固有の機能も、特に問題を引き起こします。

  • 仮想機能。実行時にどのサブクラスが存在し、どのサブクラスが実際にインスタンス化されるかを知らなければ、最終プログラムに存在する必要がある仮想関数を知ることはできません。リンカには、それに関する十分な情報がないため、すべてを保持する必要があります。
  • コンストラクターを持つグローバル、およびそのコンストラクター。一般に、リンカはグローバルのコンストラクタに副作用がないことを認識できないため、実行する必要があります。これは明らかに、グローバル自体も保持する必要があることを意味します。

どちらの場合も、仮想関数またはグローバル変数コンストラクターによって使用される何かも保持する必要があります。

追加の注意点として、共有ライブラリを構築している場合、GCCのデフォルト設定は共有ライブラリ内のすべての関数をエクスポートし、可能な限り「使用」されることです。リンカが関係しています。これを修正するには、エクスポートの代わりにシンボルを非表示にするようにデフォルトを設定する必要があります(例:-fvisibility=hiddenを使用)、エクスポートする必要があるエクスポートされた関数を明示的に選択します。

34
olsner

G ++を使用している場合は、このフラグ-Wunusedを使用できます

文書によると:

変数が宣言とは別に使用されていないとき、関数が静的であるが定義されていないとき、ラベルが宣言されているが使用されていないとき、ステートメントが明示的に使用されていない結果を計算するときは常に警告します。

http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html

Edit:他の便利なフラグ-Wunreachable-codeによると、ドキュメントによると:

このオプションは、ある条件が満たされないため、または戻らないプロシージャの後にあるため、少なくともソースコードの行全体が実行されないことをコンパイラが検出した場合に警告することを目的としています。

Update:同様のトピックが見つかりました レガシーC/C++プロジェクトでのデッドコード検出

25
UmmaGumma

コードカバレッジ ツールを探していると思います。コードカバレッジツールは、実行中のコードを分析し、実行されたコードの行と実行回数、および実行されなかったコードを通知します。

このオープンソースコードカバレッジツールにチャンスを与えてみてください: TestCocoon -C/C++およびC#のコードカバレッジツール。

18
Carlos V

ここでの本当の答えは次のとおりです:本当に確実に知ることはできません。

少なくとも、些細なケースでは、すべてを手に入れたかどうかはわかりません。 到達不能コードに関するウィキペディアの記事 の以下を考慮してください。

double x = sqrt(2);
if (x > 5)
{
  doStuff();
}

ウィキペディアが正しく指摘しているように、賢いコンパイラーはこのようなものをキャッチできるかもしれません。ただし、変更を検討してください。

int y;
cin >> y;
double x = sqrt((double)y);

if (x != 0 && x < 1)
{
  doStuff();
}

コンパイラはこれをキャッチしますか?多分。しかし、それを行うには、定数スカラー値に対してsqrtを実行する以上のことを行う必要があります。 (double)yは常に整数(簡単)であることを把握し、整数のセット(ハード)のsqrtの数学的な範囲を理解する必要があります。非常に洗練されたコンパイラーは、sqrt関数、math.hのすべての関数、またはドメインが把握できます。これは非常に複雑になり、その複雑さは基本的に無限です。洗練されたレイヤーをコンパイラーに追加し続けることができますが、入力の特定のセットに到達できないコードを潜入する方法が常にあります。

そして、単純に入力されない入力セットがあります。実際には意味をなさない入力、または他の場所の検証ロジックによってブロックされる入力。コンパイラーがそれらについて知る方法はありません。

この結果、他の人が言及したソフトウェアツールは非常に有用ですが、後でコードを手動で調べない限り、すべてをキャッチしたことを確実に知ることはできません。その場合でも、何も見逃していないという確信はありません。

唯一の本当の解決策であるIMHOは、可能な限り警戒し、自動化を自由に使用し、可能な限りリファクタリングし、コードを改善する方法を常に探します。もちろん、とにかくそうすることは良い考えです。

15
Justin Morgan

私自身は使用していませんが、 cppcheck 、未使用の関数を見つけたと主張しています。おそらく完全な問題を解決することはできませんが、それは出発点かもしれません。

12
Mr Shark

Gimple SoftwareのPC-lint/FlexeLint を使用してみてください。と主張する

プロジェクト全体で未使用のマクロ、typedef、クラス、メンバー、宣言などを見つける

静的解析に使用し、非常に優れていることがわかりましたが、特にデッドコードを見つけるために使用しなかったことを認めなければなりません。

9
Tony

コンパイルエラーを発生させずに、パブリック関数および変数をプライベートまたは保護としてマークします。これを行う間、コードのリファクタリングも試みてください。関数をプライベートにし、ある程度保護することで、プライベート関数は同じクラスからしか呼び出せないため、検索領域を縮小しました(アクセス制限を回避するための愚かなマクロや他のトリックがない限り、その場合はお勧めします)新しい仕事を見つける)。現在作業中のクラスのみがこの関数を呼び出すことができるため、プライベート関数は必要ないと判断する方がはるかに簡単です。コードベースに小さなクラスがあり、疎結合である場合、この方法は簡単です。コードベースに小さなクラスがないか、非常に密な結合がある場合は、まずそれらをクリーンアップすることをお勧めします。

次に、残りのすべてのパブリック関数をマークし、クラス間の関係を把握するために呼び出しグラフを作成します。このツリーから、ブランチのどの部分がトリムできるように見えるかを把握してください。

この方法の利点は、モジュールごとに実行できることです。そのため、コードベースが破損している場合でも、長時間を費やすことなく、簡単にユニットテストに合格できます。

4
Lie Ryan

未使用のものを見つけるための私の通常のアプローチは

  1. ビルドシステムが依存関係の追跡を正しく処理することを確認します
  2. フルスクリーンのターミナルウィンドウを備えた2番目のモニターをセットアップし、繰り返しビルドを実行し、最初の画面いっぱいの出力を表示します。 watch "make 2>&1"は、Unixでトリックを行う傾向があります。
  3. ソースツリー全体で検索と置換操作を実行し、すべての行の先頭に「//?」を追加します
  4. 「//?」を削除して、コンパイラによってフラグが立てられた最初のエラーを修正します対応する行で。
  5. エラーがなくなるまで繰り返します。

これは多少時間がかかるプロセスですが、良い結果が得られます。

4
Simon Richter

私は実際にそのようなことをするツールを使用したことはありません...しかし、私がすべての答えで見た限り、誰もこの問題が計算不可能であると言ったことはありません。

これはどういう意味ですか?この問題は、コンピューター上のアルゴリズムでは解決できないこと。この定理(このようなアルゴリズムは存在しない)は、チューリングの停止問題の結果です。

使用するツールはすべてアルゴリズムではなく、ヒューリスティックです(つまり、正確なアルゴリズムではありません)。使用されないすべてのコードを正確に提供するわけではありません。

3
geekazoid

Linuxを使用している場合は、callgrindスイートの一部であるC/C++プログラム分析ツールであるvalgrindを調べてください。このツールには、メモリリークやその他のメモリエラーをチェックするツールも含まれています。 (これも使用する必要があります)。プログラムの実行中のインスタンスを分析し、そのコールグラフに関するデータ、およびコールグラフ上のノードのパフォーマンスコストに関するデータを生成します。通常はパフォーマンス分析に使用されますが、アプリケーションの呼び出しグラフも生成するため、どの関数が呼び出されたか、呼び出し元を確認できます。

これは明らかに、ページの他の場所で言及されている静的メソッドを補完するものであり、完全に未使用のクラス、メソッド、および関数を削除する場合にのみ役立ちます。実際に呼び出されるメソッド内のデッドコードを見つけるのには役立ちません。

3
Adam Higuera

1つの方法は、デバッガーと、コンパイル中に未使用のマシンコードを削除するコンパイラー機能を使用することです。

一部のマシンコードが削除されると、デバッガーはソースコードの対応する行にbreakpojntを配置できません。だからあなたはどこにでもブレークポイントを置き、プログラムを開始し、ブレークポイントを検査します-「このソースにコードがロードされていない」状態にあるものは除去されたコードに対応しますこれら2つのうちどちらが発生したかを分析します。

少なくともVisual Studioでの動作はこれであり、他のツールセットでも同様に機能すると思います。

これは大変な作業ですが、すべてのコードを手動で分析するよりも速いと思います。

2
sharptooth

アプリケーションの作成に使用するプラットフォームによって異なります。

たとえば、Visual Studioを使用する場合、コードを解析およびプロファイリングできる 。NET ANTS Profiler などのツールを使用できます。この方法では、コードのどの部分が実際に使用されているかをすぐに知る必要があります。 Eclipseにも同等のプラグインがあります。

それ以外の場合、アプリケーションのどの機能がエンドユーザーによって実際に使用されているかを知る必要があり、アプリケーションを簡単にリリースできる場合は、監査にログファイルを使用できます。

各メイン関数について、その使用状況をトレースし、数日/週後にそのログファイルを取得して、それを確認できます。

1
AUS

CppDepend は、未使用のタイプ、メソッド、およびフィールドを検出でき、さらに多くのことができる商用ツールです。 WindowsおよびLinuxで利用できますが(現在64ビットのサポートはありません)、2週間の試用版が付属しています。

免責事項:私はそこで働きませんが、このツールのライセンスを所有しています(.NETコードのより強力な代替手段である NDepend も所有しています)。

好奇心が強い人のために、 CQLinq で書かれた、デッドメソッドを検出するための組み込み(カスタマイズ可能な)ルールの例を以下に示します。

// <Name>Potentially dead Methods</Name>
warnif count > 0
// Filter procedure for methods that should'nt be considered as dead
let canMethodBeConsideredAsDeadProc = new Func<IMethod, bool>(
    m => !m.IsPublic &&       // Public methods might be used by client applications of your Projects.
         !m.IsEntryPoint &&            // Main() method is not used by-design.
         !m.IsClassConstructor &&      
         !m.IsVirtual &&               // Only check for non virtual method that are not seen as used in IL.
         !(m.IsConstructor &&          // Don't take account of protected ctor that might be call by a derived ctors.
           m.IsProtected) &&
         !m.IsGeneratedByCompiler
)

// Get methods unused
let methodsUnused = 
   from m in JustMyCode.Methods where 
   m.NbMethodsCallingMe == 0 && 
   canMethodBeConsideredAsDeadProc(m)
   select m

// Dead methods = methods used only by unused methods (recursive)
let deadMethodsMetric = methodsUnused.FillIterative(
   methods => // Unique loop, just to let a chance to build the hashset.
              from o in new[] { new object() }
              // Use a hashet to make Intersect calls much faster!
              let hashset = methods.ToHashSet()
              from m in codeBase.Application.Methods.UsedByAny(methods).Except(methods)
              where canMethodBeConsideredAsDeadProc(m) &&
                    // Select methods called only by methods already considered as dead
                    hashset.Intersect(m.MethodsCallingMe).Count() == m.NbMethodsCallingMe
              select m)

from m in JustMyCode.Methods.Intersect(deadMethodsMetric.DefinitionDomain)
select new { m, m.MethodsCallingMe, depth = deadMethodsMetric[m] }
1
Roman Boiko

今日、友人にこの質問を聞かせてもらい、有望なClangの開発を見ました。 ASTMatcher sおよび Static Analyzer これにより、コンパイル中にデッドコードセクションを判断するための十分な可視性が得られる可能性がありますが、次のことがわかりました。

https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables

これは、参照されていないシンボルを識別する目的で設計されているように見えるいくつかのGCCフラグの使用方法のほぼ完全な説明です。

0
Steven Lu

自動的にできるとは思いません。

コードカバレッジツールを使用しても、実行するには十分な入力データを提供する必要があります。

Coverity'sLLVMコンパイラ などの非常に複雑で高価格な静的解析ツールが役立つ場合があります。

しかし、私は確信が持てず、手動でコードをレビューすることを好みます。

更新済み

まあ..未使用の変数を削除するだけですが、未使用の関数は難しくありません。

更新済み

他の回答とコメントを読んだ後、私はそれができないとより強く確信しています。

意味のあるコードカバレッジ測定を行うには、コードを知っている必要があります。また、カバレッジの結果を準備/実行/レビューするよりも多くの手動編集が高速であることがわかっている場合。

0
9dan

何らかの関数が呼び出される場合の一般的な問題は、NP完全です。チューリングマシンが停止するかどうかわからないため、何らかの関数が呼び出されるかどうかを一般的な方法で事前に知ることはできません。 main()から記述した関数への(静的な)パスがあれば取​​得できますが、それが呼び出されることを保証するものではありません。

0
Luis Colorado