未使用のコードを削除することになっているレガシーC++コードがあります。問題は、コードベースが大きいことです。
どのコードが呼び出されない/使用されないかを確認するにはどうすればよいですか?
未使用のコードを削除することになっているレガシーC++コードがあります。問題は、コードベースが大きいことです。
どのコードが呼び出されない/使用されないかを確認するにはどうすればよいですか?
未使用のコードには次の2種類があります。
最初の種類の場合、優れたコンパイラが役立ちます。
-Wunused
(GCC、Clang)は未使用の変数について警告する必要があります。Clangの未使用のアナライザーは、(使用されていても)読み取られない変数について警告するようにインクリメントされています。-Wunreachable-code
(古いGCC、2010年に削除)アクセスされないローカルブロックについて警告する必要があります(早期の返品または常にtrueと評価される条件で発生します)catch
コンパイラは通常、例外がスローされないことを証明できないため、未使用のブロックについて警告するオプションはありません。2番目の種類の場合、それははるかに困難です。静的にはプログラム全体の分析が必要であり、リンク時間の最適化によってデッドコードが実際に削除される場合でも、実際には、プログラムは実行時に非常に変換されているため、ユーザーに意味のある情報を伝えることはほぼ不可能です。
したがって、2つのアプローチがあります。
gcov
適切に機能するために、コンパイル中に特定のフラグを渡す必要があることに注意してください)。さまざまな入力(単体テストまたは非回帰テスト)の適切なセットを使用してコードカバレッジツールを実行すると、デッドコードは必然的に未到達のコード内にあります...したがって、ここから開始できます。このテーマに非常に興味があり、実際に自分でツールを作成する時間と傾向がある場合は、Clangライブラリを使用してそのようなツールを作成することをお勧めします。
Clangがコードを解析し、オーバーロード解決を実行するため、C ++言語のルールに対処する必要がなく、目前の問題に集中することができます。
ただし、この種の手法では、使用されていない仮想オーバーライドを特定できません。これは、仮想オーバーライドが、理由がわからないサードパーティのコードによって呼び出される可能性があるためです。
未使用の整関数(および未使用のグローバル変数)の場合、GCCとGNU ldを使用していれば、GCCは実際にほとんどの作業を実行できます。
ソースをコンパイルするときはとを使用-ffunction-sections
し-fdata-sections
、リンクするときは使用を使用します-Wl,--gc-sections,--print-gc-sections
。リンカは、呼び出されなかったために削除された可能性のあるすべての関数と、参照されなかったすべてのグローバルを一覧表示します。
(もちろん、その--print-gc-sections
部分をスキップして、リンカーに関数をサイレントに削除させることもできますが、それらはソースに保持されます。)
注:これは未使用の完全な関数のみを検出し、関数内のデッドコードについては何もしません。ライブ関数のデッドコードから呼び出された関数も保持されます。
一部のC++固有の機能も、特に問題を引き起こします。
どちらの場合も、仮想関数またはグローバル変数コンストラクターによって使用されるものはすべて保持する必要があります。
追加の注意点は、共有ライブラリを構築している場合、GCCのデフォルト設定は共有ライブラリ内のすべての関数をエクスポートし、リンカーに関する限り、その関数が「使用」されることです。これを修正するには、エクスポートするのではなく、デフォルトでシンボルを非表示に設定する必要があります(たとえば-fvisibility=hidden
、を使用)。次に、エクスポートする必要のあるエクスポートされた関数を明示的に選択します。
g ++を使用している場合は、このフラグを使用できます-Wunused
ドキュメントによると:
変数が宣言以外で使用されていない場合、関数が静的であると宣言されているが定義されていない場合、ラベルが宣言されているが使用されていない場合、およびステートメントが明示的に使用されていない結果を計算する場合は常に警告します。
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
編集-Wunreachable-code
:
ドキュメントによると、他の便利なフラグがあります:
このオプションは、ある条件が満たされないため、またはプロシージャの後に戻ることがないために、少なくともソースコードの行全体が実行されないことをコンパイラが検出した場合に警告することを目的としています。
更新:レガシーC /C++プロジェクトで同様のトピックのデッドコード検出を見つけました
あなたはコードカバレッジツールを探していると思います。コードカバレッジツールは、実行中のコードを分析し、実行されたコードの行と回数、および実行されなかった行を通知します。
このオープンソースコードカバレッジツールにチャンスを与えることを試みることができます:TestCocoon -C / C ++およびC#用のコードカバレッジツール。
ここでの本当の答えは次のとおりです。あなたは本当に確実に知ることは決してできません。
少なくとも、重要なケースでは、すべてを取得したかどうかを確認することはできません。到達不能コードに関するウィキペディアの記事から次のことを考慮してください。
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
整数のセットの数学的範囲を理解する必要があります(難しい)。非常に洗練されたコンパイラーは、関数、 math.hsqrt
内のすべての関数、または定義域を把握できる任意の固定入力関数に対してこれを実行できる場合があります。これは非常に複雑になり、複雑さは基本的に無限です。コンパイラに洗練されたレイヤーを追加し続けることはできますが、特定の入力セットでは到達できないコードを忍び込む方法は常にあります。
そして、単純に入力されない入力セットがあります。実生活では意味をなさない入力、または他の場所の検証ロジックによってブロックされる入力。コンパイラがそれらについて知る方法はありません。
この結果、他の人が言及したソフトウェアツールは非常に便利ですが、後で手動でコードを実行しない限り、すべてを確実にキャッチしたことを知ることはできません。それでも、何も見逃していないことを確信することはできません。
唯一の本当の解決策であるIMHOは、可能な限り警戒し、自動化を自由に使用し、可能な場合はリファクタリングし、コードを改善する方法を常に探すことです。もちろん、とにかくそれを行うのは良い考えです。
私自身は使ったことがありませんが、cppcheckは未使用の関数を見つけたと主張しています。それはおそらく完全な問題を解決することはありませんが、それは始まりかもしれません。
GimpleSoftwareのPC-lint/FlexeLintを使用してみてください。それは
プロジェクト全体で未使用のマクロ、typedef、クラス、メンバー、宣言などを検索します
私はそれを静的分析に使用し、非常に優れていることを発見しましたが、デッドコードを具体的に見つけるためにそれを使用していないことを認めなければなりません。
未使用のものを見つけるための私の通常のアプローチは
watch "make 2>&1"
Unixでトリックを行う傾向があります。これはやや長いプロセスですが、良い結果が得られます。
コンパイルエラーを発生させずに、できるだけ多くのパブリック関数と変数をプライベートまたはプロテクトとしてマークします。これを実行している間、コードのリファクタリングも試みてください。関数をプライベートにし、ある程度保護することで、プライベート関数は同じクラスからのみ呼び出すことができるため、検索領域を減らすことができます(アクセス制限を回避するための愚かなマクロやその他のトリックがない限り、その場合はお勧めします新しい仕事を見つける)。現在作業しているクラスのみがこの関数を呼び出すことができるため、プライベート関数が不要であると判断する方がはるかに簡単です。このメソッドは、コードベースに小さなクラスがあり、緩く結合されている場合に簡単です。コードベースに小さなクラスがない場合、または非常に緊密な結合がある場合は、最初にそれらをクリーンアップすることをお勧めします。
次に、残りのすべてのパブリック関数にマークを付け、コールグラフを作成して、クラス間の関係を把握します。このツリーから、ブランチのどの部分をトリミングできるように見えるかを調べてみてください。
この方法の利点は、モジュールごとに実行できることです。そのため、コードベースが壊れている場合でも、長期間を費やすことなく、単体テストに合格し続けることが容易になります。
私は実際にそのようなことをするツールを使ったことがありません...しかし、私がすべての答えで見た限り、誰もこの問題が計算できないと言ったことはありません。
これはどういう意味ですか?この問題は、コンピューター上のどのアルゴリズムでも解決できないこと。この定理(そのようなアルゴリズムは存在しない)は、チューリングの停止性問題の結果です。
使用するすべてのツールはアルゴリズムではなく、ヒューリスティックです(つまり、正確なアルゴリズムではありません)。使用されていないすべてのコードが正確に提供されるわけではありません。
Linuxを使用している場合callgrind
は、スイートの一部であるC / C ++プログラム分析ツールを調べることをお勧めします。このツールにvalgrind
は、メモリリークやその他のメモリエラー(これも使用する必要があります)をチェックするツールも含まれています。プログラムの実行中のインスタンスを分析し、そのコールグラフ、およびコールグラフ上のノードのパフォーマンスコストに関するデータを生成します。これは通常、パフォーマンス分析に使用されますが、アプリケーションのコールグラフも生成するため、呼び出された関数とその呼び出し元を確認できます。
これは明らかに、ページの他の場所で説明されている静的メソッドを補完するものであり、完全に未使用のクラス、メソッド、および関数を削除する場合にのみ役立ちます。実際に呼び出されるメソッド内のデッドコードを見つけるのには役立ちません。
1つの方法は、コンパイル中に未使用のマシンコードを削除するデバッガーとコンパイラー機能を使用することです。
一部のマシンコードが削除されると、デバッガーはソースコードの対応する行にブレークポイントを配置できなくなります。したがって、ブレークポイントをどこにでも配置してプログラムを起動し、ブレークポイントを検査します。「このソースにコードがロードされていない」状態のブレークポイントは、削除されたコードに対応します。そのコードは呼び出されないか、インライン化されているため、最小限の実行が必要です。これら2つのどちらが発生したかを見つけるための分析。
少なくとも、それがVisual Studioでどのように機能するかであり、他のツールセットでもそれができると思います。
これは大変な作業ですが、すべてのコードを手動で分析するよりも速いと思います。
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] }
これは、アプリケーションの作成に使用するプラットフォームによって異なります。
たとえば、Visual Studioを使用している場合は、コードを解析およびプロファイリングできる.NETANTSProfilerなどのツールを使用できます。このようにして、コードのどの部分が実際に使用されているかをすばやく知る必要があります。Eclipseにも同等のプラグインがあります。
それ以外の場合、アプリケーションのどの機能がエンドユーザーによって実際に使用されているかを知る必要があり、アプリケーションを簡単にリリースできる場合は、監査にログファイルを使用できます。
主な機能ごとに、その使用状況を追跡でき、数日/週後にそのログファイルを取得して確認できます。
自動的にできるとは思いません。
コードカバレッジツールを使用する場合でも、実行するのに十分な入力データを提供する必要があります。
CoverityやLLVMコンパイラなどの非常に複雑で高価な静的分析ツールが役立つ場合があります。
しかし、よくわかりません。手動でコードを確認したいと思います。
更新しました
まあ..未使用の変数を削除するだけですが、未使用の関数は難しくありません。
更新しました
他の回答やコメントを読んだ後、私はそれができないことをより強く確信しています。
意味のあるコードカバレッジ測定値を得るには、コードを知っている必要があります。また、多くの手動編集がカバレッジ結果の準備/実行/レビューよりも高速であることがわかっている場合。
今日、友人にこの質問をしてもらいました。たとえば、ASTMatcherや、コンパイル中にデッドコードセクションを特定するために十分な可視性を備えている可能性のあるStatic Analyzerなど、いくつかの有望なClang開発を見回しました。これを見つけました:
https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables
これは、参照されていないシンボルを識別する目的で設計されているように見えるいくつかのGCCフラグの使用方法のほぼ完全な説明です。
いくつかの関数が呼び出されるかどうかの一般的な問題は、NP完全です。チューリングマシンが停止するかどうかわからないため、何らかの関数が呼び出されるかどうかを一般的な方法で事前に知ることはできません。main()から作成した関数へのパスが(静的に)あるかどうかを確認できますが、それが呼び出されることを保証するものではありません。
GNUリンカには、--cref
相互参照情報を生成するオプションがあります。gcc
これは、コマンドラインから。を介して渡すことができます-Wl,--cref
。
たとえば、がで使用されるfoo.o
シンボルを定義するとします。次に、出力に次のように表示されます。foo_sym
bar.o
foo_sym foo.o
bar.o
foo_sym
に限定されている場合foo.o
、追加のオブジェクトファイルは表示されません。その後に別の記号が続きます。
foo_sym foo.o
force_flag options.o
さて、これからはそれが使われていないことがわかりませんfoo_sym
。これは単なる候補です。1つのファイルで定義されており、他のファイルでは使用されていないことがわかっています。foo_sym
で定義し、そこで使用することができますfoo.o
。
したがって、この情報を使用して行うことは
static
ます。もちろん、これらのシンボルの一部は動的リンク用にエクスポートされるため(実行可能ファイルがリンクされている場合でもそうなる可能性があります)、意図的に使用されていない可能性は無視しています。それはあなたが知っていて賢く対処しなければならないより微妙な状況です。
g ++を使用している場合は、このフラグを使用できます-Wunused
ドキュメントによると:
Warn whenever a variable is unused aside from its declaration, whenever a function is declared static but never defined, whenever a label is declared but not used, and whenever a statement computes a result that is explicitly not used.
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
編集:ここに他の有用なフラグがあります-Wunreachable-codeドキュメントによると:
This option is intended to warn when the compiler detects that at least a whole line of source code will never be executed, because some condition is never satisfied or because it is after a procedure that never returns.