c-effective Cソース中で、実際に使用されるコードを判別する(自作編)

目的

#ifdefが複雑にネストしているCソースファイル中で、どの部分が有効かを簡単に調べたい。
背景と動機については id:taiyo:20080202#p1 などを参照。
Cプリプロセスの部分適応ツールについては昨日(id:taiyo:20080204#p1)試したので、今回はそれ以外にどんな方法があるかを検討する。

結果

3通りの方法を実施した。

名前 方法 判別の精度 事前の準備 備考
show-run オブジェクト内のデバッグ情報を使う 低い 簡単 デバッグ情報付きでのコンパイルが必要
show-eff Cプリプロセッサ出力を使う 高い やや面倒 gcc-3.0.1以前相当のコンパイラにはパッチ宛が必要
cpp-parse マクロ定義結果を元にプリプロセス処理を自前で行う 中くらい やや面倒

1と2については完了、3については挑戦中。

以下、詳細。

show-run - オブジェクト内のデバッグ情報を使う(自作ツール)

概要

コンパイル時に -g オプションなどでデバッグ情報を付加した場合、オブジェクトファイル内にソースコードの行番号が埋め込まれている(objdump に -l オプションを付けると表示される)。
これを使って、ソース中の実行される行を特定できないか?

メリット

準備が簡単。コンパイル時には通常はデバッグ情報を付加しているので、コンパイル後のオブジェクトファイルとソースコードがあれば、他に用意するものは無い。

デメリット

精度が低い。この方法は、要はソースコードデバッガでブレークポイントを設定できる行を特定することと同じ。デバッガを使った人ならわかるだろうが、実行される行でもインライン展開や最適化によってブレークポイントが設定できない場合がある。なので、この方法では実行されるのに実行マークが付かない場合がありうる。

表示スクリプト

オブジェクトのデバッグ情報とソースコードから、実行されるコードを表示するscriptを作成した。

show-operate-lines.rb
https://sssvn.jp/svn/spikelet/c/cparser/tool/show-operate-lines.rb
実行例

以下のソースを例にする:

hello.c
https://sssvn.jp/svn/spikelet/c/cparser/sample/hello.c
#include <stdio.h>
#define DEBUG
#ifdef NONEXISTENT
#define LOOP_NUM 10
#else
#define LOOP_NUM 100
#endif

static inline void
dummy(void)
{
  printf("inside dummy\n");
}

int
main(void)
{
  int i;
  for (i=0; i<LOOP_NUM; i++) {
    printf("Hello, world.\n");
#ifdef DEBUG
    printf("loop counter is %d.\n", i);
#endif
  }
  dummy();
  return 0;
}

デバッグ情報付きでコンパイル:

% gcc -Wall -g -O2 -c -o hello.o hello.c

表示スクリプト実行:

    1 - #include <stdio.h>
    2 - #define DEBUG
    3 - #ifdef NONEXISTENT
    4 - #define LOOP_NUM 10
    5 - #else
    6 - #define LOOP_NUM 100
    7 - #endif
    8 -
    9 - static inline void
   10 - dummy(void)
   11 - {
   12 T   printf("inside dummy\n");
   13 - }
   14 -
   15 - int
   16 - main(void)
   17 T {
   18 -   int i;
   19 T   for (i=0; i<LOOP_NUM; i++) {
   20 T     printf("Hello, world.\n");
   21 - #ifdef DEBUG
   22 T     printf("loop counter is %d.\n", i);
   23 - #endif
   24 -   }
   25 -   dummy();
   26 -   return 0;
   27 T }

行番号のあとに 'T' とあるのが、実行される行。
見ての通り、プリプロセッサ命令行と、inline関数の呼び出し行、auto変数定義行にマークが付いていない。

まあ、このレベルでも、デバッガをいちいち立ち挙げて確認するのに比べたら、便利と言える。

B:Cプリプロセッサ出力を使う

(後で書く)

C:マクロ定義結果を元にプリプロセス処理を自前で行う

(後で書く)