テストステ論

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

(macro-of-inline report) 元の関数定義を消さないことにより, 関数ポインタでの呼び出しもサポートする

関数ポインタの言い換えについてもっとも顕著な例は,

#include <stdio.h>
static inline void f(void) { puts("hoge"); }
void g(void (*fun)(void)) { fun(); }
int main(void) { g(f); return 0; }

である. このプログラムは"hoge"を出力する.

  • fは明らかにマクロ化対象の関数である
  • funに入ってるのは, fである.
  • ではfun()呼び出しをマクロ関数呼び出しの形に変形する必要がある(namespaceをつける必要がある)がどうやって???

ここで問題になっているのは, マクロ化した場合に, ガード節など複数returnに対応するため, namespaceが必要となっていることである(マクロは展開後, フラットになってしまうから, それぞれのラベルを違うものとするために, 関数呼び出しごとにユニークなnamespace(というか正確にはprefixだが, マクロオブインラインではnamespaceと呼んでいる)が必要となる).

従って, もし, void関数をマクロ化をした場合でもその呼出元に変化がないならば, renameのことも何も気にする必要ない. 私たちが今問題としてるのは, 呼び出しもとの変形だ. しかし, 今までの検討によれば, マクロ化された関数を呼び出す場合には, namespaceをつける必要がある.

  • 関数の出口は一つである
  • 関数はラベルと持たない

と言った異常な制約をつければ, この問題は解決しそうだ. 確かに, 世の中には, これを強要するコーディング規約も存在するため, そのような場合には変形を緩やかにするというモードをつけることはアリだと思うが, まずは, 一般的な場合について間違いなく変換出来る手法を考えるべきと思う. ソフトウェア開発では, まずある程度限界まで検討してしまってから緩和する方が最終的に良い物が出来る.

あるいは, 上記のように, inline化した関数をポインタとして扱わないという制約をつければこの問題はなくなるが, テストに使うことの出来るコードを限定してしまうため(そしてそれを検査することが不可能), やはりこの形も許容した上で一般的な形を考える方がいい.

ある関数定義の任意のコード行において, それが引数や宣言由来のものかどうかは判定がつく. マクロ化した関数についても, このようにリネームされた場合については元の関数を呼び出す.

アルゴリズムは単純である.

  1. 関数内でFuncCallに出会う.
  2. それが関数内でリネームされていない場合, マクロ化の呼び出し形に変形する.
  3. そうでない場合, 形を保存する(残した関数が呼ばれる)

このように「危ない時はもとの関数を呼べるようにして, まじで安全な時のみマクロ化呼び出しとする」というアルゴリズムはバグりにくい. カーネルなどではよく行われることである.