如何使用 C 预处理器编写 while 循环?
我从教育/黑客的角度问这个问题(我真的不想这样编码)。
是否可以仅使用 C 预处理器指令来实现 while 循环。 我知道宏不能递归扩展,那么这是如何实现的呢?
I am asking this question from an educational/hacking point of view, (I wouldn't really want to code like this).
Is it possible to implement a while loop only using C preprocessor directives. I understand that macros cannot be expanded recursively, so how would this be accomplished?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
如果要实现 while 循环,则需要在预处理器中使用递归。 进行递归的最简单方法是使用延迟表达式。 延迟表达式是需要更多扫描才能完全扩展的表达式:
为什么这很重要? 当宏被扫描和扩展时,它会创建一个禁用上下文。 此禁用上下文将导致引用当前扩展宏的标记被涂成蓝色。 因此,一旦它被涂成蓝色,宏将不再扩展。 这就是宏不递归扩展的原因。 然而,禁用上下文仅在一次扫描期间存在,因此通过推迟扩展,我们可以防止宏被涂成蓝色。 我们只需要对表达式应用更多扫描。 我们可以使用这个
EVAL
宏来做到这一点:接下来,我们定义一些运算符来执行一些逻辑(例如 if 等):
现在有了所有这些宏,我们可以编写一个递归
WHILE宏。 我们使用
WHILE_INDIRECT
宏来递归地引用自身。 这可以防止宏被涂成蓝色,因为它将在不同的扫描上扩展(并使用不同的禁用上下文)。WHILE
宏采用谓词宏、运算符宏和状态(即可变参数)。 它不断将此运算符宏应用于状态,直到谓词宏返回 false(即 0)。出于演示目的,我们将创建一个谓词来检查参数数量何时为 1:
接下来我们创建一个运算符,我们将仅连接两个标记。 我们还创建一个最终运算符(称为
M
)来处理最终输出:然后使用
WHILE
宏:当然,任何类型的谓词或运算符都可以传递给它。
If you want to implement a while loop, you will need to use recursion in the preprocessor. The easiest way to do recursion is to use a deferred expression. A deferred expression is an expression that requires more scans to fully expand:
Why is this important? Well when a macro is scanned and expanding, it creates a disabling context. This disabling context will cause a token, that refers to the currently expanding macro, to be painted blue. Thus, once its painted blue, the macro will no longer expand. This is why macros don't expand recursively. However, a disabling context only exists during one scan, so by deferring an expansion we can prevent our macros from becoming painted blue. We will just need to apply more scans to the expression. We can do that using this
EVAL
macro:Next, we define some operators for doing some logic(such as if, etc):
Now with all these macros we can write a recursive
WHILE
macro. We use aWHILE_INDIRECT
macro to refer back to itself recursively. This prevents the macro from being painted blue, since it will expand on a different scan(and using a different disabling context). TheWHILE
macro takes a predicate macro, an operator macro, and a state(which is the variadic arguments). It keeps applying this operator macro to the state until the predicate macro returns false(which is 0).For demonstration purposes, we are just going to create a predicate that checks when number of arguments are 1:
Next we create an operator, which we will just concat two tokens. We also create a final operator(called
M
) that will process the final output:Then using the
WHILE
macro:Of course, any kind of predicate or operator can be passed to it.
看一下 Boost 预处理器 库,它允许您在预处理器中编写循环等等。
Take a look at the Boost preprocessor library, which allows you to write loops in the preprocessor, and much more.
您使用递归包含文件。 不幸的是,循环的迭代深度不能超过预处理器允许的最大深度。
事实证明,C++ 模板是图灵完备的,并且可以以类似的方式使用。 查看生成式编程
You use recursive include files. Unfortunately, you can't iterate the loop more than the maximum depth that the preprocessor allows.
It turns out that C++ templates are Turing Complete and can be used in similar ways. Check out Generative Programming
我使用元模板编程来达到这个目的,一旦你掌握了它的窍门,它就会很有趣。 谨慎使用时有时非常有用。 因为如上所述,它的图灵完备性甚至可以导致编译器进入无限循环或堆栈溢出! 没有什么比去喝杯咖啡却发现你的编译使用了 30+ GB 的内存和所有 CPU 来编译你的无限循环代码更好的了!
I use meta-template programming for this purpose, its fun once you get a hang of it. And very useful at times when used with discretion. Because as mentioned its turing complete, to the point where you can even cause the compiler to get into an infinite loop, or stack-overflow! There is nothing like going to get some coffee just to find your compilation is using up 30+ gigabytes of memory and all the CPU to compile your infinite loop code!
当编译器变得暴躁并且不会为我展开某些循环时,我发现这个方案很有用
但恕我直言,如果您需要比这更复杂的东西,那么您应该编写自己的预处理器。 例如,您的预处理器可以为您生成适当的头文件,并且很容易将此步骤包含在 Makefile 中,以便通过单个命令顺利编译所有内容。 我已经做到了。
I found this scheme useful when the compiler got cranky and wouldn't unroll certain loops for me
But IMHO, if you need something much more complicated than that, then you should write your own pre-preprocessor. Your pre-preprocessor could for instance generate an appropriate header file for you, and it is easy enough to include this step in a Makefile to have everything compile smoothly by a single command. I've done it.
好吧,并不是说它是一个 while 循环,而是一个计数器循环,尽管如此,该循环在干净的 CPP 中是可能的(没有模板,也没有 C++)
well, not that it's a while loop, but a counter loop, nonetheless the loop is possible in clean CPP (no templates and no C++)
这是对合法完成规则的滥用。 编写您自己的 C 预处理器。 让它按照您想要的方式解释一些#pragma 指令。
Here's an abuse of the rules that would get it done legally. Write your own C preprocessor. Make it interpret some #pragma directives the way you want.