cpp-tools - Cソース中の大量のifdefを読みやすくしたい(ツール探索編)
目的
#ifdefが複雑にネストしているCソースファイル中で、どの部分が有効かを簡単に調べたい。
背景と動機については id:taiyo:20080202#p1 などを参照。
結果
C FAQ(Question 10.18)で紹介されている3つのツールと、手元にあったツール1つを試した。
名前 | 処理可能なディレクティブ | 処理方法 | 感想 |
---|---|---|---|
rmifdef | #ifdef, #ifndef, #else | 不明(バイナリ配布) | 判定対象が狭く、あまり使い出がない |
unifdef | #ifdef, #ifndef, #else | 独自の文字列処理 | 出力エラーでソースが乱れるのが致命的 |
scpp | 全ディレクティブ | lex&yac | マクロ展開までされるのと、#if 0を処理しないのがやっかい |
pcpp | 全ディレクティブ | 不明(バイナリしか持ってない) | 不都合は今のところみつからず |
pcppが、機能面では不満がなさそう。ただ、これって公開されてるのか?作者の方に聞いてみよう。
以下、詳細。
なお、まとめでの評価は、○=メリット、×=デメリット、△=微妙な点、としている。
rmifdef(オープンソースツール)
情報源: rmifdef 使用方法
まとめ
- ○#ifdef, #ifndef, #else のみ処理可能、ネストにも対応済み
- ×#if には非対応
- ×'#' と 'if' の間の空白は許さない
- △判別できない箇所は無視(そのまま出力)するので、出力結果が不正なコードになることはない
結論:判定できるディレクティブが少ないので、あまりありがたみがない。
インストール
Windows版のバイナリのみを配布している。
http://www.din.or.jp/~itoh01/ProgramLibrary/RMIFDEF.LZH から入手。
実行方法
Windows上のcmd.exe、もしくはcygwinにて以下を実行:
実行例1
- ソース
- (単一の #ifdef)
#ifdef A "A is defined"; A; #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; A; "Finished.";実行例2
- ソース
- (ネストした #ifdef)
#ifdef A "A is defined"; A; #ifdef B "B is defined"; B; #else "B is not defined"; #endif #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- ネストにも対応している
"A is defined"; A; "B is not defined"; "Finished.";実行例3
- ソース
- (単一の #if)
#if defined(A) "A is defined"; A; #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- #if には未対応のため、ソースがそのまま出力される
実行例4
- ソース
- (ネストした #if)
#if defined(A) "A is defined"; A; #if defined(B) "B is defined"; B; #else "B is not defined"; #endif #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- ネストも当然未対応のため、ソースがそのまま出力される
実行例5
- ソース
- (#if 0)
#if 0 "Inside zero"; #else "Outside zero"; #endif "Finished.";
- 出力(実行結果)
- 未対応のため、ソースがそのまま出力される
実行例6
- ソース
- (#if 1)
#if 1 "Inside one"; #else "Outside one"; #endif "Finished.";
- 出力(実行結果)
- 未対応のため、ソースがそのまま出力される
実行例7
- ソース
- (#elif)
#ifdef A "A is defined"; A; #elif B>1 "B > 1"; B; #else "B <= 1"; B; #endif "Finished.";
- 出力(実行結果)
- 未対応なので、途中で異常終了する
"A is defined"; A; #elif: cannot analyze!実行例8
- ソース
- ('#' と 'if' の間にスペース)
# ifdef A "A is defined"; A; # else "A is not defined"; # endif "Finished.";
- 出力(実行結果)
- 未対応のため、ソースがそのまま出力される
unifdef(オープンソースツール)
情報源: unifdef – Freecodeまとめ
結論: 出力が不正になる(しかもそれを返り値などで知ることができない)のでは、使いようがない。
- ○#ifdef, #ifndef, #else のみ処理可能、ネストにも対応済み
- ×#if には非対応
- ×判別できないディレクティブ(#else)があると、出力が不正なコードになる
インストール
% wget http://freshmeat.net/redir/unifdef/10933/url_tgz/unifdef-1.0.tar.gz % tar xzvf unifdef-1.0.tar.gz % cd unifdef-1.0 % make clean % make配布tar ballになぜかオブジェクトファイルまで含まれるので、make cleanが必要。実行方法
% ./unifdef -DA -UB実行例1
- ソース
- (単一の #ifdef)
#ifdef A "A is defined"; A; #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; A; "Finished.";実行例2
- ソース
- (ネストした #ifdef)
#ifdef A "A is defined"; A; #ifdef B "B is defined"; B; #else "B is not defined"; #endif #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- ネストにも対応している
"A is defined"; A; "B is not defined"; "Finished.";実行例3
- ソース
- (単一の #if)
#if defined(A) "A is defined"; A; #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- #if には未対応のため、ソースがそのまま出力される
実行例4
- ソース
- (ネストした #if)
#if defined(A) "A is defined"; A; #if defined(B) "B is defined"; B; #else "B is not defined"; #endif #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- ネストも当然未対応のため、ソースがそのまま出力される
実行例5
- ソース
- (#if 0)
#if 0 "Inside zero"; #else "Outside zero"; #endif "Finished.";
- 出力(実行結果)
- 未対応のため、ソースがそのまま出力される
実行例6
- ソース
- (#if 1)
#if 1 "Inside one"; #else "Outside one"; #endif "Finished.";
- 出力(実行結果)
- 未対応のため、ソースがそのまま出力される
実行例7
- ソース
- (#elif)
#ifdef A "A is defined"; A; #elif B>1 "B > 1"; B; #else "B <= 1"; B; #endif "Finished.";
- 出力(実行結果)
- 未対応なのだが、出力がおかしなことになる
"A is defined"; A; #elif B>1 "B > 1"; B; "Finished.";実行例8
- ソース
- ('#' と 'if' の間にスペース)
# ifdef A "A is defined"; A; # else "A is not defined"; # endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; A; "Finished.";scpp(オープンソースツール)
情報源: プログラミング・ツール(移植ソフト)まとめ(mada)
結論:他のツールよりも判定は広く、かつ(構文解析をしているので)信頼が置けそうだが、マクロ定義までされる点はちょっと厄介。
- ○#if, #ifdef, #ifndef, #else, #elif 全て処理可能、ネストにも対応済み
- △プリプロセッサ行だけでなく、ソースコード中のマクロも展開される(マクロ展開の位置がおかしいように思うが…)
- ×#if 0 は削除できない(なぜ?)
- ×#ifdef〜#elif という組合せに非対応、出力結果が不正なコードになる
- ○内部でlexとyaccを使って、まじめに(本物のCプリプロセッサと同じように)トークン解析とパース処理をしている
インストール
% wget ftp://sunsite.unc.edu/pub/Linux/devel/lang/c/scpp-0.1.tgz % tar xzvf scpp-0.1.tgz % cd scpp-0.1 % mv lex.l lex.l.orig % sed '/undef input/i\# define YY_NO_INPUT' lex.l.orig > lex.l % make実行方法
% ./scpp -DA -MB実行例1
- ソース
- (単一の #ifdef)
#ifdef A "A is defined"; A; #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; ;1 "Finished.";実行例2
- ソース
- (ネストした #ifdef)
#ifdef A "A is defined"; A; #ifdef B "B is defined"; B; #else "B is not defined"; #endif #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- ネストにも対応している
"A is defined"; ;1 "B is not defined"; "Finished.";実行例3
- ソース
- (単一の #if)
#if defined(A) "A is defined"; A; #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; ;1 "Finished.";実行例4
- ソース
- (ネストした #if)
#if defined(A) "A is defined"; A; #if defined(B) "B is defined"; B; #else "B is not defined"; #endif #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; ;1 "B is not defined"; "Finished.";実行例5
- ソース
- (#if 0)
#if 0 "Inside zero"; #else "Outside zero"; #endif "Finished.";
- 出力(実行結果)
- 未対応のため、ソースがそのまま出力される
実行例6
- ソース
- (#if 1)
#if 1 "Inside one"; #else "Outside one"; #endif "Finished.";
- 出力(実行結果)
- 未対応のため、ソースがそのまま出力される
実行例7
- ソース
- (#elif)
#ifdef A "A is defined"; A; #elif B>1 "B > 1"; B; #else "B <= 1"; B; #endif "Finished.";
- 出力(実行結果)
- #ifdef 〜 #elif 〜 #endif という記述は認識しないのでコードがおかしくなる(この書き方はgccのプリプロセッサは受け付ける→Else - The C Preprocessor)
"A is defined"; ;1 #elif B>1 "B > 1"; B; #else "B <= 1"; B; #endif "Finished.";実行例8
- ソース
- ('#' と 'if' の間にスペース)
# ifdef A "A is defined"; ;1 # else "A is not defined"; # endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; A; "Finished.";pcpp(オープンソースツール?)
情報源: なし(社内のある方が作成したツール)まとめ
結論:機能面では十分。
- ○#if, #ifdef, #ifndef, #else, #elif 全て処理可能、ネストにも対応済み
- ○#if 0 も削除できる(-Nオプション)
インストール
CVSからなんか適当にひっぱってきたり。実行方法
% ./pcpp -DA -UB -N実行例1
- ソース
- (単一の #ifdef)
#ifdef A "A is defined"; A; #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; A; "Finished.";実行例2
- ソース
- (ネストした #ifdef)
#ifdef A "A is defined"; A; #ifdef B "B is defined"; B; #else "B is not defined"; #endif #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- ネストにも対応している
"A is defined"; A; "B is not defined"; "Finished.";実行例3
- ソース
- (単一の #if)
#if defined(A) "A is defined"; A; #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; A; "Finished.";実行例4
- ソース
- (ネストした #if)
#if defined(A) "A is defined"; A; #if defined(B) "B is defined"; B; #else "B is not defined"; #endif #else "A is not defined"; #endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; A; "B is not defined"; "Finished.";実行例5
- ソース
- (#if 0)
#if 0 "Inside zero"; #else "Outside zero"; #endif "Finished.";
- 出力(実行結果)
- 対応している
"Outside zero"; "Finished.";実行例6
- ソース
- (#if 1)
#if 1 "Inside one"; #else "Outside one"; #endif "Finished.";
- 出力(実行結果)
- 対応している
"Inside one"; "Finished.";実行例7
- ソース
- (#elif)
#ifdef A "A is defined"; A; #elif B>1 "B > 1"; B; #else "B <= 1"; B; #endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; A; "Finished.";実行例8
- ソース
- ('#' と 'if' の間にスペース)
# ifdef A "A is defined"; A; # else "A is not defined"; # endif "Finished.";
- 出力(実行結果)
- 対応している
"A is defined"; A; "Finished.";