テストステ論

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

(macro-of-inline) 破滅した

マクロオブインラインはすべてのインライン関数をマクロ化出来るわけではない.

明らかにダメなものは, 再帰関数や, 可変長引数(...)を持っているもの. これらははじくことにしている.

私はそれ以外ならば, マクロ化出来ると思っていたが, 出来ないことが今朝分かった. マクロオブインラインは, 安全にマクロ化出来る関数を大域的な情報をもとに判定しなければならないと思う. 以下のコードを見てみよう.

if (f()) {}

よくある形だ. 例えばfは, isXXXのような問い合わせ関数を想像出来る. この形がマクロ化出来ない理由は,

  1. ifのcond部分は, 式をとる.
  2. マクロは式になり得ない(GCC拡張のstatement expressionを使えば可能だが, GCCはinlineをサポートしている).

一方で関数呼び出しは式である. この違いが原因となって, マクロ化出来ないことがあるのだ. 2について解決出来る方法があれば教えて欲しい. カンマ演算子は, もとの関数が複雑な場合(例えばforループを持っていたら), 利用出来ない.

一昨日までに実装したコードを昨日捨ててさらにやり直して, なお間違いに気づいたのだから, この鬱っぷりは想像いただけるだろう.

しかしまだ希望はある. 関数呼び出しは, 「文の中では安全にマクロ化出来る」(たぶん)のだ. 具体的には, compound statementの中で呼び出された関数は, 仮にそれが返り値を持っていてネストしている場合(e.g. f(g()), gをマクロ化する)でも, 階層の深いものの返り値に適当な変数を与えてくくりだすことによって(int r = g(); f(r)), マクロ化出来る(int r; g(&r); f(r)).

なので, 「compoundで"のみ"呼ばれている関数」を対象にしてマクロ化することは出来る.

しかし言葉でいうことは簡単だが, 実装はとてもややこしい. それ故に, 上の理屈が本当に正しいのか, 判断することが出来るか分からない. マクロオブインラインはソースコードの書き換えを行う. 変換後のコードは, 変換前の面影はほとんどなく, 問題があったとしても何が問題なのか極めて判断しにくい. なので, 出来るだけシンプルで安全な方法によってのみマクロ化を実現したい.

私に不安をもたらすものの一つは, ある関数呼び出しを辿った時, それがcompoundの中であることをどうやって切り分けるかだ. 例えば以下のコードを考えよう.

if (1) {
  if (f()) {
  }
} 

fは明らかに, マクロ化がダメであるが, (外側のifの)compoundの中で呼ばれている. 従って, これを弾くためには, ifノードのcond部分は無視すると実装にする必要があるのだが, これはすなわち「無視する箇所が漏れたら終わり」ということを意味する. このように「網羅的に考える」ことを強要するのではなく, 「多少漏れていても最善ではないが正しく動く 」方がアルゴリズムとしてはロバストである. 今の考えは明らかにキレキレすぎて危険なので, もっと堅牢なアルゴリズムはないものかと考えているところである.

案があったらTwitter(@akiradeveloper)などで教えてほしい. 私はコンパイラの授業を受けたこともない. コンパイラの授業を受けたことがある人には自明な問題かも知れない.