読者です 読者をやめる 読者になる 読者になる

テストステ論

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

(macro-of-inline report) return文を考慮したinline関数からマクロへの変形

さきほど, ラベルにスコープがないことがマクロ化において問題となるということを述べた.

ラベルの扱いについて決定を行う. macro-of-inlineでは, ラベルを使用しているinline関数のマクロ化について, サポートし"ない".

理由は簡単である. ラベルはアセンブリレベルでのジャンプを実現してしまうため, もはやC言語の構文を無視している. つまり, ラベルがどこどこにあって〜〜とASTのレベルで解釈しても構文的には何の意味もないのだ. ただそのラベルにジャンプするという以外は. ifやwhileによる制御を裏切ってしまうラベルを持っていれば, サポート外とする.

幸い, ラベルがあるかどうかは調べば簡単に分かる. そもそも, ラベルの使用はあまり良いものとされていない(ガードの使用が良いとされるのとは対照的に). Linuxカーネルでは関数のエラー処理にラベルを使っているしこれは良いパターンだと思うが, 通常は, ラベルは使わない. というわけで, サポート外とする.

イレギュラーなジャンプが関数ブロック途中からブロックを抜けるreturnしかないと仮定すると, これをサポートすることが可能になってくる. これは, 関数の返り値型がvoid/non-voidに依らない. ざっくりとしたイメージであるが, f1, f2(->non-void), f3(->void)について, 以下のようなマクロを出力しようと思う. これだけで何をしようとしているのかは理解されると思う.

#include <stdio.h>

#define f1(exit) \
({ \
        int ret; \
        ret = 1; \
        goto exit; \
exit: \
        ret; \
})

#define f2(exit) \
({ \
        int ret; \
        if (1) { \
                ret = 2; \
                goto exit; \
        } \
        ret = 3; \
        goto exit; \
exit: \
        ret; \
})

#define f3(exit) \
do { \
        int ret; \
        x = 1; \
        if (x) goto exit; \
        x = 3; \
exit: \
        ; \
} while (0)

main()
{
        int x = 0;
        int r;
        r = f1(exit1);
        printf("%d\n", r);
        printf("%d\n", f1(exit1a));
        r = f2(exit2);
        printf("%d\n", r);
        f3(exit3);
        printf("%d\n", x);
}

今, プリプロセッサを通したあとのコードには, マクロ化対象のコードがあり, マクロ化した関数は, そのファイル内でしか使われない. つまり, 呼び出し元をすべて書換えることが可能である. マクロにexitというラベルを変数同様ランダムに渡し, 衝突のないようにする. これはつまり, プリプロセッサを通すことを強要するということである. 私はプリプロセッサを通さない手法についても実装しようと思っていたが, ラベルのことを考えるととても複雑になってくると思う(プリプロセッサを自前で実装するのと同等以上に難しい)ので, 諦める. ガードの非推奨までやってしまうと, 今度はmacro-of-inlineの有用性が低くなりすぎる.

関数呼び出しはFuncCallというASTノードがあるため, これを全部visitして, マクロ化した関数である場合は, この関数呼び出しのパラメータリストの末尾にランダムな名前を入れればいい.

と簡単に言ってみたが, 同時にnon-void返しな関数へのサポートも行ってしまうため, まぁまぁ骨である. 意識の高い時に一気にやることになる.


(追記)

void返しの方はともかく, non-void返しの方はダメですね. 構文エラーです.

http://melpon.org/wandbox/permlink/TnRKYwBeucHuhfKa

#include <stdio.h>

int main()
{
    printf("%d¥n", ({int x; x=1; x=2;}));
    printf("%d¥n", ({int x; x=1; x=2; x;}));
    return 0;
}

は, 以下のように怒られます. ひぃ.

prog.c: In function 'main':
prog.c:5:21: error: ISO C forbids braced-groups within expressions [-Wpedantic]
     printf("%d¥n", ({int x; x=1; x=2;}));
                     ^
prog.c:5:27: warning: variable 'x' set but not used [-Wunused-but-set-variable]
     printf("%d¥n", ({int x; x=1; x=2;}));
                           ^
prog.c:6:21: error: ISO C forbids braced-groups within expressions [-Wpedantic]
     printf("%d¥n", ({int x; x=1; x=2; x;}));
                     ^

返り値を返す時はカンマ演算子を使うのが上等手段なのですが, 複雑なブロックに対しては無理じゃなイカ

さーてどうするかなー


さっきの書き方はGCCの拡張であって, statement expressionというものらしい. つまり, これを使うとGCCでしか使えないがGCCはinlineをサポートしているので, 結果, うわぁってなる

gcc - Are compund statements (blocks) surrounded by parens expressions in ANSI C? - Stack Overflow