如何用 C 预处理器编写一个 while 循环?
Posted
技术标签:
【中文标题】如何用 C 预处理器编写一个 while 循环?【英文标题】:How to write a while loop with the C preprocessor? 【发布时间】:2010-09-24 01:49:41 【问题描述】:我从教育/黑客的角度提出这个问题,(我真的不想这样编码)。
是否可以仅使用 C 预处理器指令来实现 while 循环。我知道宏不能递归扩展,那么如何实现呢?
【问题讨论】:
【参考方案1】:如果你想实现一个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
为什么这很重要?那么当一个宏被扫描和扩展时,它会创建一个禁用上下文。此禁用上下文将导致引用当前扩展宏的标记被涂成蓝色。因此,一旦将其涂成蓝色,宏将不再展开。这就是宏不递归扩展的原因。但是,禁用上下文仅在一次扫描期间存在,因此通过延迟扩展,我们可以防止宏被涂成蓝色。我们只需要对表达式应用更多扫描。我们可以使用这个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__)))
接下来我们创建一个运算符,我们将连接两个标记。我们还创建了一个最终操作符(称为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
当然,任何类型的谓词或运算符都可以传递给它。
【讨论】:
能否给宏添加一点解释?这真的很有趣,但很难得到。所有这些交叉引用。我没有得到最后一行。什么是PRED
,因为它是通过采用 x
来定义的,在这里:M(EVAL(WHILE(**PRED**, OP, x, y, z))) //Expands to xyz
它什么都不带
更多关于这一切如何运作的解释,中间有更多步骤:here 或here。这些文章中的示例与此答案中的示例并不完全相同,但它们足够接近。
美丽的答案 - 如果它会起作用。但不是xyz
,而是扩展为WHILE ( PRED, OP, xy, z )
,参见。 coliru.stacked-crooked.com/a/1a7b4e7879bd7d4b
@Armali 糟糕,有一个错字。它应该是OBSTRUCT(WHILE_INDIRECT)
而不是DEFER(WHILE_INDIRECT)
。见:coliru.stacked-crooked.com/a/a381ddcbcae0b890【参考方案2】:
看看Boost preprocessor 库,它允许您在预处理器中编写循环等等。
【讨论】:
【参考方案3】:您使用递归包含文件。不幸的是,循环的迭代次数不能超过预处理器允许的最大深度。
事实证明,C++ 模板是图灵完备的,并且可以以类似的方式使用。查看Generative Programming
【讨论】:
【参考方案4】:我为此目的使用元模板编程,一旦你掌握了它就会很有趣。有时在谨慎使用时非常有用。因为如前所述,它的图灵完备,甚至可以导致编译器进入无限循环或堆栈溢出!没有什么比去喝杯咖啡更能发现你的编译使用了 30+ GB 的内存和所有的 CPU 来编译你的无限循环代码!
【讨论】:
【参考方案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]);
【讨论】:
【参考方案6】:这是对可以合法完成的规则的滥用。编写自己的 C 预处理器。让它按照你想要的方式解释一些#pragma 指令。
【讨论】:
这不是在回答问题,它明确表示“the C 预处理器”,而不是“a C 预处理器”。这显然是指现有的工具链。【参考方案7】:当编译器变得暴躁并且不会为我展开某些循环时,我发现这个方案很有用
#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 预处理器编写一个 while 循环?的主要内容,如果未能解决你的问题,请参考以下文章