web-dev-qa-db-ja.com

Cプリプロセッサでwhileループを書く方法は?

教育/ハッキングの観点からこの質問をしています(このようなコードは実際にはしたくないでしょう)。

[〜#〜] c [〜#〜]プリプロセッサディレクティブのみを使用してwhileループを実装することは可能ですか?マクロは再帰的に展開できないことを理解していますが、これをどのように実現するのでしょうか?

66
Tarski

Boost preprocessor ライブラリを見てください。これにより、プリプロセッサでループを書くことができます。

9
CesarB

Whileループを実装する場合は、プリプロセッサで再帰を使用する必要があります。再帰を行う最も簡単な方法は、遅延式を使用することです。遅延式は、完全に展開するためにより多くのスキャンを必要とする式です。

#define EMPTY()
#define DEFER(id) id EMPTY()
#define OBSTRUCT(id) id DEFER(EMPTY)()
#define EXPAND(...) __VA_ARGS__

#define A() 123
A() // Expands to 123
DEFER(A)() // Expands to A () because it requires one more scan to fully expand
EXPAND(DEFER(A)()) // Expands to 123, because the EXPAND macro forces another scan

何でこれが大切ですか?マクロがスキャンされて展開されると、無効化コンテキストが作成されます。この無効化コンテキストは、現在拡大しているマクロを参照するトークンを青く塗りつぶします。したがって、一度青く塗られると、マクロは展開されなくなります。これが、マクロが再帰的に展開しない理由です。ただし、無効にするコンテキストは1回のスキャン中にのみ存在するため、展開を延期することにより、マクロが青く塗られるのを防ぐことができます。式にさらにスキャンを適用する必要があります。このEVALマクロを使用してそれを行うことができます。

#define EVAL(...)  EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define EVAL4(...) EVAL5(EVAL5(EVAL5(__VA_ARGS__)))
#define EVAL5(...) __VA_ARGS__

次に、ロジック(ifなど)を実行するための演算子を定義します。

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)

#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 ~, 1,

#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0

#define BOOL(x) COMPL(NOT(x))

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define IF(c) IIF(BOOL(c))

これらすべてのマクロを使用して、再帰的なWHILEマクロを作成できます。 WHILE_INDIRECTマクロを使用して、再帰的に自分自身を参照します。これにより、マクロが別のスキャンで展開される(および別の無効化コンテキストを使用する)ため、マクロが青くペイントされなくなります。 WHILEマクロは、述語マクロ、演算子マクロ、および状態(可変引数)です。述語マクロがfalse(0)を返すまで、この演算子マクロを状態に適用し続けます。

#define WHILE(pred, op, ...) \
    IF(pred(__VA_ARGS__)) \
    ( \
        OBSTRUCT(WHILE_INDIRECT) () \
        ( \
            pred, op, op(__VA_ARGS__) \
        ), \
        __VA_ARGS__ \
    )
#define WHILE_INDIRECT() WHILE

デモンストレーションのために、引数の数が1の場合にチェックする述語を作成します。

#define NARGS_SEQ(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define NARGS(...) NARGS_SEQ(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)

#define IS_1(x) CHECK(PRIMITIVE_CAT(IS_1_, x))
#define IS_1_1 ~, 1,

#define PRED(x, ...) COMPL(IS_1(NARGS(__VA_ARGS__)))

次に、2つのトークンを連結する演算子を作成します。また、最終出力を処理する最終演算子(Mと呼ばれる)を作成します。

#define OP(x, y, ...) CAT(x, y), __VA_ARGS__ 
#define M(...) CAT(__VA_ARGS__)

次に、WHILEマクロを使用します。

M(EVAL(WHILE(PRED, OP, x, y, z))) //Expands to xyz

もちろん、あらゆる種類の述語または演算子をそれに渡すことができます。

102
Paul Fultz II

再帰的なインクルードファイルを使用します。残念ながら、プリプロセッサが許可する最大の深さを超えてループを繰り返すことはできません。

C++テンプレートはチューリング完全であり、同様の方法で使用できることがわかりました。チェックアウト Generative Programming

10
Andru Luvisi

私はこの目的のためにメタテンプレートプログラミングを使用しています。また、慎重に使用すると非常に便利です。既に述べたように、チューリングが完了しているため、コンパイラーが無限ループまたはスタックオーバーフローに陥る可能性さえあります!コンパイルが30ギガバイト以上のメモリとすべてのCPUを使用して無限ループコードをコンパイルしていることを確認するためにコーヒーを飲みに行くようなものはありません!

5
Robert Gould

これは、合法的に行われるルールの乱用です。独自のCプリプロセッサを作成します。いくつかの#pragmaディレクティブを希望どおりに解釈させます。

5

まあ、それはwhileループではなく、カウンターループですが、それでもループはクリーンなCPPで可能です(テンプレートもC++もありません)

#ifdef pad_always

#define pad(p,f) p##0

#else

#define pad0(p,not_used) p
#define pad1(p,not_used) p##0

#define pad(p,f) pad##f(p,)

#endif

// f - padding flag
// p - prefix so far
// a,b,c - digits
// x - action to invoke

#define n0(p,x)
#define n1(p,x)         x(p##1)
#define n2(p,x) n1(p,x) x(p##2)
#define n3(p,x) n2(p,x) x(p##3)
#define n4(p,x) n3(p,x) x(p##4)
#define n5(p,x) n4(p,x) x(p##5)
#define n6(p,x) n5(p,x) x(p##6)
#define n7(p,x) n6(p,x) x(p##7)
#define n8(p,x) n7(p,x) x(p##8)
#define n9(p,x) n8(p,x) x(p##9)

#define n00(f,p,a,x)                       n##a(pad(p,f),x)
#define n10(f,p,a,x) n00(f,p,9,x) x(p##10) n##a(p##1,x)
#define n20(f,p,a,x) n10(f,p,9,x) x(p##20) n##a(p##2,x)
#define n30(f,p,a,x) n20(f,p,9,x) x(p##30) n##a(p##3,x)
#define n40(f,p,a,x) n30(f,p,9,x) x(p##40) n##a(p##4,x)
#define n50(f,p,a,x) n40(f,p,9,x) x(p##50) n##a(p##5,x)
#define n60(f,p,a,x) n50(f,p,9,x) x(p##60) n##a(p##6,x)
#define n70(f,p,a,x) n60(f,p,9,x) x(p##70) n##a(p##7,x)
#define n80(f,p,a,x) n70(f,p,9,x) x(p##80) n##a(p##8,x)
#define n90(f,p,a,x) n80(f,p,9,x) x(p##90) n##a(p##9,x)

#define n000(f,p,a,b,x)                           n##a##0(f,pad(p,f),b,x)
#define n100(f,p,a,b,x) n000(f,p,9,9,x) x(p##100) n##a##0(1,p##1,b,x)
#define n200(f,p,a,b,x) n100(f,p,9,9,x) x(p##200) n##a##0(1,p##2,b,x)
#define n300(f,p,a,b,x) n200(f,p,9,9,x) x(p##300) n##a##0(1,p##3,b,x)
#define n400(f,p,a,b,x) n300(f,p,9,9,x) x(p##400) n##a##0(1,p##4,b,x)
#define n500(f,p,a,b,x) n400(f,p,9,9,x) x(p##500) n##a##0(1,p##5,b,x)
#define n600(f,p,a,b,x) n500(f,p,9,9,x) x(p##600) n##a##0(1,p##6,b,x)
#define n700(f,p,a,b,x) n600(f,p,9,9,x) x(p##700) n##a##0(1,p##7,b,x)
#define n800(f,p,a,b,x) n700(f,p,9,9,x) x(p##800) n##a##0(1,p##8,b,x)
#define n900(f,p,a,b,x) n800(f,p,9,9,x) x(p##900) n##a##0(1,p##9,b,x)

#define n0000(f,p,a,b,c,x)                               n##a##00(f,pad(p,f),b,c,x)
#define n1000(f,p,a,b,c,x) n0000(f,p,9,9,9,x) x(p##1000) n##a##00(1,p##1,b,c,x)
#define n2000(f,p,a,b,c,x) n1000(f,p,9,9,9,x) x(p##2000) n##a##00(1,p##2,b,c,x)
#define n3000(f,p,a,b,c,x) n2000(f,p,9,9,9,x) x(p##3000) n##a##00(1,p##3,b,c,x)
#define n4000(f,p,a,b,c,x) n3000(f,p,9,9,9,x) x(p##4000) n##a##00(1,p##4,b,c,x)
#define n5000(f,p,a,b,c,x) n4000(f,p,9,9,9,x) x(p##5000) n##a##00(1,p##5,b,c,x)
#define n6000(f,p,a,b,c,x) n5000(f,p,9,9,9,x) x(p##6000) n##a##00(1,p##6,b,c,x)
#define n7000(f,p,a,b,c,x) n6000(f,p,9,9,9,x) x(p##7000) n##a##00(1,p##7,b,c,x)
#define n8000(f,p,a,b,c,x) n7000(f,p,9,9,9,x) x(p##8000) n##a##00(1,p##8,b,c,x)
#define n9000(f,p,a,b,c,x) n8000(f,p,9,9,9,x) x(p##9000) n##a##00(1,p##9,b,c,x)

#define n00000(f,p,a,b,c,d,x)                                   n##a##000(f,pad(p,f),b,c,d,x)
#define n10000(f,p,a,b,c,d,x) n00000(f,p,9,9,9,9,x) x(p##10000) n##a##000(1,p##1,b,c,d,x)
#define n20000(f,p,a,b,c,d,x) n10000(f,p,9,9,9,9,x) x(p##20000) n##a##000(1,p##2,b,c,d,x)
#define n30000(f,p,a,b,c,d,x) n20000(f,p,9,9,9,9,x) x(p##30000) n##a##000(1,p##3,b,c,d,x)
#define n40000(f,p,a,b,c,d,x) n30000(f,p,9,9,9,9,x) x(p##40000) n##a##000(1,p##4,b,c,d,x)
#define n50000(f,p,a,b,c,d,x) n40000(f,p,9,9,9,9,x) x(p##50000) n##a##000(1,p##5,b,c,d,x)
#define n60000(f,p,a,b,c,d,x) n50000(f,p,9,9,9,9,x) x(p##60000) n##a##000(1,p##6,b,c,d,x)
#define n70000(f,p,a,b,c,d,x) n60000(f,p,9,9,9,9,x) x(p##70000) n##a##000(1,p##7,b,c,d,x)
#define n80000(f,p,a,b,c,d,x) n70000(f,p,9,9,9,9,x) x(p##80000) n##a##000(1,p##8,b,c,d,x)
#define n90000(f,p,a,b,c,d,x) n80000(f,p,9,9,9,9,x) x(p##90000) n##a##000(1,p##9,b,c,d,x)

#define cycle5(c1,c2,c3,c4,c5,x) n##c1##0000(0,,c2,c3,c4,c5,x)
#define cycle4(c1,c2,c3,c4,x) n##c1##000(0,,c2,c3,c4,x)
#define cycle3(c1,c2,c3,x) n##c1##00(0,,c2,c3,x)
#define cycle2(c1,c2,x) n##c1##0(0,,c2,x)
#define cycle1(c1,x) n##c1(,x)

#define concat(a,b,c) a##b##c

#define ck(arg) a[concat(,arg,-1)]++;
#define SIZEOF(x) (sizeof(x) / sizeof((x)[0]))

void check5(void)
{
    int i, a[32769];

    for (i = 0; i < SIZEOF(a); i++) a[i]=0;

    cycle5(3,2,7,6,9,ck);

    for (i = 0; i < SIZEOF(a); i++) if (a[i] != 1) printf("5: [%d] = %d\n", i+1, a[i]);
}
5
Vlad

コンパイラーが不機嫌になり、特定のループを展開しない場合に、このスキームが役立つことがわかりました

#define REPEAT20(x){x; x; x; x; x; x; x; x; x; x; x; x; x; x; x; x; x; x; x; x;}

REPEAT20(val = pleaseconverge(val));

しかし、私見では、それよりもはるかに複雑なものが必要な場合は、独自のプリプリプロセッサを作成する必要があります。たとえば、プリプリプロセッサで適切なヘッダーファイルを生成できます。この手順をMakefileに含めると、1つのコマンドですべてをスムーズにコンパイルできます。やった.

2
Mikael

あなたが尋ねたものとはまったく異なりますが、これらのリンクを有効なメイクファイルおよびシェルスクリプトでもあるCプログラムにチェックアウトしてください。

C、make、およびShellコードは相互にビルドし、Cプログラム(?)を作成します。これは、シェルスクリプトとして実行されると、メイクファイルを使用してCコンパイラーでコンパイルされます。

2000難読化されたCコンテストの勝者。

http://www.ioccc.org/2000/tomx.c
http://www.ioccc.org/2000/tomx.hint

1
Kevin Beck