dump-macros - Cファイル内でdefineされているマクロ一覧を出力する
目的
Cのソースファイル中で、どの #ifdef が有効なのかを調べるために、そのソース内でのdefine済みマクロ一覧を簡単に取得する。
結果
gccのwrapperを作ることで実現できた。
- オリジナルコマンドのファイル名をgcc-origに変更する
- マクロ表示用gccラッパーをオリジナルコマンド名で置く
- gcc-wrapper (https://sssvn.jp/svn/spikelet/c/dump-macros/gcc-wrapper)
- wrapper内では、プリプロセッサオプション(-dM,-E)を使ってマクロ定義を出力させる
コンパイル時間への影響も、10%未満。
背景と動機
Cのソースは、#ifdef 〜 #endif などがとかく混ざりこみやすい(特に、複数プラットフォーム対応などで)。
たいてい、configureを実行して必要なマクロ定義ファイルを作り、その定義内容にしたがってソースの有効箇所を切り替えるつくりになっている。
#ifdefがソース中にあまりに多いと、今のconfigでどの部分が有効なのかがわかりにくくなってくる。
また、マクロ定義内容がconfigによって変わるような場合も、マクロ展開結果が今どうなっているのかの把握が面倒になる。
対策として、まずは今のconfigでdefineされているマクロ一覧を取得することからはじめる。
GCC(プリプロセッサ)の機能
これらを使えば、個々のファイルのマクロ定義は取得できる。
しかし、コンパイラ(含むプリプロセッサ)はたいていmakeの中から呼ばれ、そこでは環境変数や引数の設定を色々と行ってくれている。実際のコンパイルと同じ環境を用意してからプリプロセッサを起動しないと、得られるマクロ定義も異なってしまう。
gcc-wrapper
引数を加工してコマンドを起動するだけなので、shell scriptで十分対応できそうに思うが、受け取った引数を加工してから適切にクオートして別プロセスに渡す、という処理が意外と難しい。これは、shell scriptでは引数が単なるスペース区切りの文字列になってしまうから。
なので、引数を文字列の配列で渡してもらえるように、rubyでwrapperを作成した(単に使い慣れてるからと、Cで書くのは面倒だったから)。
なお、gccで依存関係の自動出力機能を使っている場合(Automakeを使うと勝手にそうなる)には、依存関係出力関連のオプションを取り除かないとgcc(プリプロセッサ)が何をすればよいか混乱するので、-Mで始まる引数を削除している。。
#!/usr/local/bin/ruby # Dump all macros which defined on compiling to *.m at same place with *.o. # MEMO: to dump macros, use gcc options -E and -dM. # MEMO: Don't wrap when compiler reads from stdin, # because second-read (original gcc do so) gets empty data. orig_command = $0 + "-orig" class Array def delete_dependency_args each_with_index{ |e,idx| if /^-(MF|MT|MQ)$/ =~ e then self[idx, 2] = false elsif /^-(M|MM|MG|MP|MD|MMD)$/ =~ e self[idx] = false end } delete(false) end end unless ARGV.include? "-" opt_c_index = ARGV.index "-c" opt_o_index = ARGV.index "-o" if opt_c_index and opt_o_index and /\.o$/ =~ ARGV[opt_o_index+1] then new_arg = ARGV.clone new_arg[opt_o_index+1] = new_arg[opt_o_index+1].sub(/\.o$/, ".m") new_arg[opt_c_index,1] = [ "-E", "-dM", "-P" ] new_arg.delete "-c" new_arg.delete_dependency_args ## STDERR.puts [ orig_command, *new_arg ].inspect system(orig_command, *new_arg) end end exec(orig_command, *ARGV)
処理概要は以下の通り
- 標準入力から読み込む場合でなく、かつ -c オプション付きでコンパイルされた場合には、
- *.o と同じ場所に *.m というファイル名でマクロ定義を出力する(プリプロセッサ呼び出し)
- そしてコンパイル実行(オリジナルgcc呼び出し)
標準入力から読む場合にマクロ出力してしまうと、コンパイラ実行時に標準入力が空になってしまうので、その場合はマクロ出力は諦めた(対策はあるが、glibcのコンパイルぐらいにしか見ないケースなのでまあ良いだろう)。