#ifdef 分岐を制御するプリプロセッサー定義の探索
由緒正しいソースコードをメンテナンスしていると、大量の #ifdef 分岐に泣かされることが、よくある。
よし、ソースコードや Makefile で定義されているプリプロセッサー(つまり #define や -D オプション)をあらかじめ列挙しよう! そうだ、分岐に使われるものだけを狙い撃ちしたら便利じゃないか? とおもいたち、条件分岐マクロに使われているプリプロセッサーを抽出するスクリプトをでっちあげてみた。:
#! /bin/sh extract_ifdef_branch() { find . -type f -name "*.h" -o -type f -name "*.c"\ |while read f; do egrep '^[[:blank:]]*#[[:blank:]]*if' $f; done } # convert tab to space, and shrink multiple spaces to one space replace_tab_to_space() { sed -e 's/\t/ /g; s/ \+/ /g' } # change ' # HOGERA' to '#HOGERA' trancate_heading_blanks() { sed -e 's/^ *# *\(.*\)$/#\1/' } # remove "#if", "#ifdef" and "#ifndef"'s remove_heading_if() { sed -e 's/^#if[^ ]* \(.*\)$/\1/' } replace_paren_and_logics_to_space() { sed -e 's/[()]/ /g; s/[<>!][=]*/ /g; s/&&/ /g; s/||/ /g' } # remove C style comment remove_comments() { sed -e 's|/\*.*\*/||g' } TMPNAME=`mktemp` extract_ifdef_branch\ |replace_tab_to_space\ |trancate_heading_blanks\ |remove_comments\ > ${TMPNAME} ( # collect "#if" branches # "#if defined" versions cat ${TMPNAME}\ |egrep '^#if +[ (!]*defined'\ |sed -e 's/defined/ /g' # other "#if", "#ifdef" and "#ifndef" versions cat ${TMPNAME}\ |egrep -e '^#if ' -e '^#ifdef' -e '^#ifndef'\ |egrep -v '^#if +[ (!]*defined' )\ |remove_heading_if\ |replace_paren_and_logics_to_space\ |sed -e 's/ \+/\n/g'\ |sort |uniq\ |egrep -v -e "^$" -e "^TRUE$" -e "^FALSE$" -e '^[0-9]*$' rm ${TMPNAME}
上記を extract-ifdef-branch.sh として保存しているとして、次を実行すると、カレントディレクトリー以下にあるファイルを全検索してマクロ分岐条件の #define 定義行をファイル名といっしょに確認できる。:
$ sh extract-ifdef-branch.sh\ |while read n;\ do echo "----- $n -----";\ egrep -rnH "^[[:blank:]]*#[[:blank:]]*define[[:blank:]]+$n" .;\ done
ただし抽出した #define は、 #ifdef の特定条件下でのみ有効だとかコメントアウトされていて無効という場合もあるので、きちんとソースを開いて確認する作業は必要。
それでもむやみに追いかけるよりは効率的 …だよね?
蛇足だけれど、 Makefile その他でプリプロセッサー定義の追加も対象にするために grep 条件に "-D<空白文字類>*$n" *1 を追加してもよいだろう。
#ifdef 系の分岐は #ifdef, #ifndef, #if defined, #if !defined, #if (!defined ... とバリエーションがある上、論理演算で結ばれていたり、バージョン番号との比較があったりと、かなり複雑。たぶん上のスクリプトには、いろいろ漏れがあるとおもうので、それぞれ気がついたら工夫して対処してほしい。
分岐を探す対象は *.h と *.c にしぼってあるし、コメントは C89 形式の /* ... */ スタイルで、ちゃんと閉じられているものしか取り除かない。 C++ に適用したい人は extract_ifdef_branch 関数や remove_comments 関数に手を入れるといい。