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.";