未使用のコードを削除することになっているレガシーC ++コードがあります。問題は、コードベースが大きいことです。
どのコードが呼び出されない、または使用されないかを知るにはどうすればよいですか?
f()
にf()
明確に解決される呼び出しがある場合、3番目の関数を追加するだけでその呼び出しを2番目に解決するf()
ことはできません。 "3番目の関数を追加すると、呼び出しが不明確になり、プログラムのコンパイルができなくなります。反例を見るのが好き(=恐ろしい)。
未使用のコードを削除することになっているレガシーC ++コードがあります。問題は、コードベースが大きいことです。
どのコードが呼び出されない、または使用されないかを知るにはどうすればよいですか?
f()
にf()
明確に解決される呼び出しがある場合、3番目の関数を追加するだけでその呼び出しを2番目に解決するf()
ことはできません。 "3番目の関数を追加すると、呼び出しが不明確になり、プログラムのコンパイルができなくなります。反例を見るのが好き(=恐ろしい)。
回答:
未使用のコードには2つの種類があります。
最初の種類については、優れたコンパイラーが役立ちます。
-Wunused
(GCC、Clang)は未使用の変数について警告する必要があります。Clang未使用アナライザーは、(使用されていても)読み取られない変数について警告するようにインクリメントされています。-Wunreachable-code
(2010年に削除された古いGCC )は、アクセスされないローカルブロックについて警告する必要があります(これは、常にtrueと評価される早期の復帰または条件で発生します)。catch
コンパイラーは通常、例外がスローされないことを証明できないため、未使用のブロックについて警告するオプションはありません。2番目の種類の場合は、はるかに困難です。静的にはプログラム全体の分析が必要であり、リンク時の最適化によって実際にデッドコードが削除される場合でも、実際にはプログラムは実行時に変換されているため、ユーザーに意味のある情報を伝えることはほとんど不可能です。
したがって、2つのアプローチがあります。
gcov
。です。コンパイル中に特定のフラグを渡して、正しく機能するようにしてください)。さまざまな入力(ユニットテストまたは非回帰テスト)の適切なセットを使用してコードカバレッジツールを実行します。デッドコードは必然的に到達されていないコード内にあるため、ここから開始できます。このテーマに非常に興味があり、実際にツールを自分で作成する時間と傾向がある場合は、Clangライブラリを使用してそのようなツールを作成することをお勧めします。
Clangがコードを解析してオーバーロードの解決を実行するため、C ++言語のルールに対処する必要がなく、目の前の問題に集中することができます。
ただし、理由のないサードパーティのコードによって呼び出される可能性があるため、この種の手法では未使用の仮想オーバーライドを識別できません。
foo()
それだけで表示されるときに「呼び出された」とマークされないようにif (0) { foo(); }
することはボーナスになりますが、特別なスマートが必要です。)
未使用の関数全体(および未使用のグローバル変数)の場合、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
ドキュメントによると:
このオプションは、コンパイラーがソースコードの少なくとも1行全体が実行されないことを検出したときに警告することを目的としています。
更新:類似したトピックであるレガシーC / C ++プロジェクトでデッドコード検出が見つかりました
-Wunused
宣言されている(または宣言されて定義されている)変数について警告しますが、実際には使用されていません。ちなみにスコープ付きガードはかなり煩わしいです:p Clangには、(Ted Kremenekによって)書き込まれるが読み取られない不揮発性変数についても警告する実験的な実装があります。-Wunreachable-code
到達できない関数内のコードについて警告します。たとえば、throw
or return
ステートメントの後にあるコード、または決して行われない分岐内のコード(トートロジー比較の場合に発生します)です。
コードカバレッジツールを探していると思います。コードカバレッジツールは、実行中のコードを分析し、実行されたコード行と実行されたコード行、および実行されなかったコード行を通知します。
このオープンソースコードカバレッジツールにチャンスを与えることを試みることができます:TestCocoon -C / C ++およびC#のコードカバレッジツール。
void func()
b.cppで使用されるa.cppに関数 があるとしましょう。どのようにコンパイラがチェックできるか、そのfunc()はプログラムで使用されていますか?それはリンカーの仕事です。
ここでの本当の答えは次のとおりです。
少なくとも、重要なケースでは、すべてを手に入れたかどうかはわかりません。到達不能コードに関するWikipediaの記事から、以下を検討してください。
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は、可能な限り警戒し、自由に自動化を使用し、可能な場合はリファクタリングし、コードを改善する方法を常に模索することです。もちろん、とにかくそうすることをお勧めします。
私自身は使用していませんが、cppcheckは未使用の関数を見つけると主張しています。それはおそらく完全な問題を解決しませんが、それは始まりかもしれません。
cppcheck --enable=unusedFunction --language=c++ .
、これらの未使用の関数を見つけるために使用します。
Gimple SoftwareのPC-lint / FlexeLintを使用してみてください。それは主張する
プロジェクト全体で未使用のマクロ、typedef、クラス、メンバー、宣言などを見つける
私はそれを静的分析に使用し、それが非常に良いことを発見しましたが、死んだコードを具体的に見つけるためにそれを使用しなかったことを認めなければなりません。
未使用のものを見つけるための私の通常のアプローチは
watch "make 2>&1"
Unixではトリックを行う傾向があります。これはやや長いプロセスですが、良い結果が得られます。
コンパイルエラーを発生させずに、パブリック関数と変数をプライベートまたはプロテクトとしてマークします。これを行う際には、コードのリファクタリングも試みます。関数をプライベートにしてある程度保護することにより、プライベート関数は同じクラスからしか呼び出せないため、検索範囲が狭くなります(アクセス制限を回避するための愚かなマクロやその他のトリックがない限り、私はあなたをお勧めします新しい仕事を見つける)。現在作業中のクラスだけがこの関数を呼び出すことができるため、プライベート関数が不要であると判断する方がはるかに簡単です。この方法は、コードベースのクラスが小さく、疎結合である場合に簡単です。コードベースに小さなクラスがないか、結合が非常に緊密でない場合は、まずそれらをクリーンアップすることをお勧めします。
次に、残りのすべてのパブリック関数をマークし、コールグラフを作成して、クラス間の関係を把握します。このツリーから、ブランチのどの部分がトリミングできるように見えるかを調べてみてください。
この方法の利点は、モジュールごとに実行できることです。そのため、コードベースが壊れている場合でも、長期間テストを行わなくても簡単にユニットテストに合格できます。
Linuxをcallgrind
使用している場合は、valgrind
スイートの一部であるC / C ++プログラム分析ツールであるを調べることをお勧めします。このツールには、メモリリークやその他のメモリエラーをチェックするツールも含まれています(これらも使用する必要があります)。プログラムの実行中のインスタンスを分析し、そのコールグラフに関するデータと、コールグラフ上のノードのパフォーマンスコストに関するデータを生成します。これは通常、パフォーマンス分析に使用されますが、アプリケーションのコールグラフも生成するため、呼び出された関数とその呼び出し元を確認できます。
これは、ページの他の場所で言及されている静的メソッドを明らかに補完するものであり、完全に未使用のクラス、メソッド、および関数を排除するためにのみ役立ちます-実際に呼び出されるメソッド内のデッドコードを見つけるのに役立ちません。
私は実際にそのようなことをするツールを使用していません...しかし、私がすべての回答で見た限り、誰もこの問題が計算不可能であると言ったことはありません。
これはどういう意味ですか?この問題は、これまでコンピュータのアルゴリズムでは解決できないこと。この定理(そのようなアルゴリズムは存在しない)は、チューリングの停止問題の帰結です。
使用するすべてのツールはアルゴリズムではなく、ヒューリスティックです(つまり、正確なアルゴリズムではありません)。使用されていないすべてのコードを正確に提供するわけではありません。
1つの方法は、デバッガーとコンパイラー機能を使用して、コンパイル中に未使用のマシンコードを排除することです。
一部のマシンコードが削除されると、デバッガーはソースコードの対応する行にbreakpojntを配置できなくなります。したがって、どこにでもブレークポイントを配置してプログラムを開始し、ブレークポイントを検査します。「このソースに対してコードがロードされていない」状態にあるものは、削除されたコードに対応します。これら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を使用している場合、.NET ANTS Profilerなどのコードを解析およびプロファイルできるツールを使用できます。このように、コードのどの部分が実際に使用されているかをすばやく知る必要があります。Eclipseにも同等のプラグインがあります。
それ以外の場合で、アプリケーションのどの機能が実際にエンドユーザーによって使用されているかを知る必要があり、アプリケーションを簡単にリリースできる場合は、監査にログファイルを使用できます。
主な機能ごとに、その使用状況を追跡し、数日/週後にそのログファイルを取得して確認することができます。
自動的にできるとは思いません。
コードカバレッジツールを使用する場合でも、実行するには十分な入力データを提供する必要があります。
CoverityやLLVMコンパイラなどの非常に複雑で高価な静的分析ツールが役立つ場合があります。
しかし、私は確信が持てず、手動のコードレビューを好むでしょう。
更新しました
まあ..未使用の変数を削除するだけで、未使用の関数は難しくありません。
更新しました
他の回答やコメントを読んだ後、それを行うことはできないと私は強く確信しています。
意味のあるコードカバレッジ測定を行うには、コードを知っている必要があります。カバレッジ結果の準備/実行/確認よりも多くの手動編集の方が速いことがわかっている場合。
私は友人に本日この質問をしてもらい、私はいくつかの有望なClangの開発、たとえばASTMatcherや静的アナライザーを見て回り、コンパイル中にデッドコードセクションを特定するための十分な可視性があるかもしれませんが、それから私はこれが見つかりました:
https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables
これは、参照されていないシンボルを識別する目的で設計されていると思われるいくつかのGCCフラグの使用方法のほぼ完全な説明です。
まあ、g ++を使用している場合は、このフラグを使用できます。
ドキュメントによると:
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.