テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(macro-of-inline report) 関数名の言い換えについて検討

ある関数コールf(x)を見た時に, これが, マクロ化すべきかどうかを判定したい.

一番ナイーブな実装は, マクロ化すべき関数名の集合を, 関数の外で計算し, これをSとして, fがSに含まれているかを単に判定するのだが, これではうまくいかないケースがある. fが言い換えである場合だ. 関数のブロック内で, 全く別の関数gについてfという名前をつけてf(x)を呼んだ場合, これは本質的には, 言い換え元の関数gであるが, まるでfを呼んだかのように見えてしまう.

つまり, まさに呼び出し位置でのSを計算する必要がある.

マクロオブインラインは今のところ, マクロ化対象の関数を関数ポインタとして一度でも扱っているプログラムは不正としている(そして, この不正をはじくことはしていない. 難しいからだ). これは概ねうまく行く. 引数としてinline関数を渡すことはCの標準が禁止しているし, 大抵はやらないだろう. しかし, 関数ポインタの配列として関数がセットされている場合はどうだろうかとか, 色々パターンを読み切っていくのはかなり険しい. C言語をそこそこやっていた人がLinuxカーネルを見て, こんな魔術があるのかと驚くほど, C言語は自由度が高い. 何かのメタプログラミング手法によって機械的に生成されたコードを含んでいても動くだろうか?とか考えると投げ出したくなる. スパっとシンプルに割り切るアルゴリズムが必要なのだろう. 場合分けは, 網羅性を要求されるため, ヤバい.

実験的に以下の構文木を取得すると,

int main()
{
        fs[i](x);

        (fs + i)(x);

        t->f(x);

        f.f(x);

        (*f)(x);

        f(x);
}
FileAST:
  FuncDef:
    Decl: main, [], [], []
      FuncDecl:
        TypeDecl: main, []
          IdentifierType: ['int']
    Compound:
      FuncCall:
        ArrayRef:
          ID: fs
          ID: i
        ExprList:
          ID: x
      FuncCall:
        BinaryOp: +
          ID: fs
          ID: i
        ExprList:
          ID: x
      FuncCall:
        StructRef: ->
          ID: t
          ID: f
        ExprList:
          ID: x
      FuncCall:
        StructRef: .
          ID: f
          ID: f
        ExprList:
          ID: x
      FuncCall:
        UnaryOp: *
          ID: f
        ExprList:
          ID: x
      FuncCall:
        ID: f
        ExprList:
          ID: x

となる. 一番最後のものだけがマクロ化可能であるとすると, FuncCallのnameメンバがIDであるものだけに注目して, 他は全部捨てるというのが合理的に思う. 通常は, その他の例のようにポインタをいじくりまわして関数コールをするということはあまりない. C言語オブジェクト指向をやるといっても, 通常は, 構造体に関数ポインタを貼り付けるまではやらない.

このため, 全部FuncCallのうち,

FuncCall:
  ID: name

の形でのみ呼ばれているものをマクロ化対象とすれば簡明である.

つまり, マクロ化可能な関数のリストを作る前処理は,

  1. 関数定義から, それが再帰だったら可変引数持ちだったり, inlineじゃなかったりしたら拒否
  2. 上記のシンプルな形以外で呼ばれている関数は拒否
  3. expression内で呼ばれている関数は拒否

として作れば, compoundの中で呼ばれているシンプルな形のコールのみを対象とすればよい, に帰着する. つまり残りは, 関数に入ったあとでの言い換えのみを回避すればいい.

今回の検討で, 「シンプルな形で呼ばれている関数のみマクロ化対象とする」がまぁまぁ良さそうなことがわかったので, 実装しようと思う. 例外的な場合に付きあわず, 常識的な場合にのみ集中して正しく書き切るしか, マクロオブインラインの精度を上げる方法はない.