在C预处理器中编写一个while循环
我从教育/黑客的angular度来问这个问题,(我不会真的想要这样的代码)。
是否有可能只使用C预处理器指令来实现一个while循环。 我明白,macros不能recursion地扩展,那么这将如何完成?
看看Boost预处理器库,它允许你在预处理器中编写循环,等等。
如果你想实现一个while循环,你将需要在预处理器中使用recursion。 recursion最简单的方法是使用延迟expression式。 延迟expression式是需要更多扫描才能完全展开的expression式:
#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
为什么这很重要? 那么当一个macros被扫描和扩展,它创build一个禁用上下文。 这种禁用的上下文将导致引用当前扩展macros的令牌被绘成蓝色。 因此,一旦被涂成蓝色,macros观将不再扩大。 这就是为什么macros不能recursion地扩展。 但是,在一次扫描中只存在禁用的上下文,所以通过推迟扩展,我们可以防止我们的macros变成蓝色。 我们只需要对expression式应用更多的扫描。 我们可以使用这个EVAL
macros来做到这一点:
#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))
现在用所有这些macros,我们可以编写一个recursion的WHILE
macros。 我们使用WHILE_INDIRECT
macros来recursion地引用自身。 这可以防止macros被绘成蓝色,因为它将在不同的扫描上展开(并使用不同的禁用上下文)。 WHILE
macros使用谓词macros,运算符macros和状态(这是可变参数)。 它继续应用这个运算符macros的状态,直到谓词macros返回假(这是0)。
#define WHILE(pred, op, ...) \ IF(pred(__VA_ARGS__)) \ ( \ DEFER(WHILE_INDIRECT) () \ ( \ pred, op, op(__VA_ARGS__) \ ), \ __VA_ARGS__ \ ) #define WHILE_INDIRECT() WHILE
为了演示的目的,我们将创build一个谓词来检查参数个数是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__)))
接下来我们创build一个操作符,我们只需要连接两个令牌。 我们还创build了一个最终操作符(称为M
)来处理最终输出:
#define OP(x, y, ...) CAT(x, y), __VA_ARGS__ #define M(...) CAT(__VA_ARGS__)
然后使用WHILE
macros:
M(EVAL(WHILE(PRED, OP, x, y, z))) //Expands to xyz
当然,任何一种谓词或操作符都可以传递给它。
您使用recursion包含文件。 不幸的是,你不能迭代循环超过预处理器允许的最大深度。
事实certificate,C ++模板是图灵完成,可以用类似的方式。 检查生成式编程
这是滥用合法的规则。 编写你自己的C预处理器。 让它按照你想要的方式解释一些#pragma指令。
我使用元模板编程来达到这个目的,它的乐趣一旦你得到它的萦绕。 有时酌情使用非常有用。 因为正如前面提到的那样,你甚至可以导致编译器进入无限循环,或者堆栈溢出。 没有什么事情可以去找点咖啡来find你的编译器正在使用30多GB的内存和所有的CPU来编译你的无限循环代码!
好吧,这不是一个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]); }
我发现这个scheme很有用,当编译器变得胡思乱想,不会为我展开某些循环
#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中包含这个步骤,以便通过一个命令顺利地编译所有的东西。 我已经做到了
不完全是你问,但结帐这些链接到一个C程序,这也是一个有效的生成文件和shell脚本。
C,make和shell代码相互构build,创build一个C程序(?),当它作为一个shell脚本执行时,它将通过C编译器使用一个makefile进行编译!
在2000混淆的C比赛中获胜。
http://www.ioccc.org/2000/tomx.c
http://www.ioccc.org/2000/tomx.hint