如何使用CPP预处理器编写递归的for循环宏来生成C代码?

7
我想强制预处理器为我进行一些自动代码生成。 我不需要很多:只需要一个包含另一个for循环的简单for循环。
我已经阅读了所有有关宏展开的内容,现在即使蓝色颜料出现时也不再发笑。 在好的日子里,我甚至可以解释为什么需要多层宏来生成带有标记粘贴的函数名称。 我实际上已经让for循环工作了。 但是当涉及到将一个循环嵌套在另一个循环中时,我就只能随机地使用DEFER、EVAL和OBSTRUCT并希望能得到最佳结果。
我不会因理性的呼吁而退缩。 我真的想使用标准的C预处理器来做到这一点。 我保证,无论结果如何,我、我的雇主和我的继承人都不会因技术过错而起诉您。 我保证,除非佩戴适当的安全眼镜,否则我不会允许任何其他人维护代码,甚至查看代码。 如果您愿意,可以假装我只是出于理论兴趣而提问。 或者,如果CPP中的递归宏很古怪,那么M4肯定是整个鸡。
我找到的最好的参考资料是一个9年前的Usenet主题讨论: http://comp.std.c.narkive.com/5WbJfCof/double-cpp-expansion 它开始离题,语气有些小气和挑衅,而且超出了我的理解范围。但我认为我寻找的答案在其中某个地方。
其次是一份名为Cloak的CPP滥用头文件的文档: https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms 它采用了一种略微不同的迭代方法,也许可以替代我所需的方法。但它也是一个很好的概述。
以下是一些简化的代码,以展示我卡住的地方。

repeat.h:

#define REPEAT(macro, times, start_n, next_func, next_arg, macro_args...) \
    _REPEAT_ ## times(macro, start_n, next_func, next_arg, ## macro_args)

#define REPEAT_ADD_ONE(macro, times, start_n, macro_args... )                    \
    REPEAT(macro, times, start_n, _REPEAT_ADD_ONE, 0, ## macro_args)

#define _REPEAT_ADD_ONE(n, ignore...) _REPEAT_ADD_ONE_ ## n

#define _REPEAT_0(args...)  /* empty */
#define _REPEAT_1(macro, n, func, i, args...) macro(n, ## args) 
#define _REPEAT_2(m, n, f, i, a...) m(n, ## a); _REPEAT_1(m, f(n, i), f, i, ## a)
#define _REPEAT_3(m, n, f, i, a...) m(n, ## a); _REPEAT_2(m, f(n, i), f, i, ## a)
#define _REPEAT_4(m, n, f, i, a...) m(n, ## a); _REPEAT_3(m, f(n, i), f, i, ## a)
#define _REPEAT_5(m, n, f, i, a...) m(n, ## a); _REPEAT_4(m, f(n, i), f, i, ## a)
#define _REPEAT_6(m, n, f, i, a...) m(n, ## a); _REPEAT_5(m, f(n, i), f, i, ## a)
#define _REPEAT_7(m, n, f, i, a...) m(n, ## a); _REPEAT_6(m, f(n, i), f, i, ## a)
#define _REPEAT_8(m, n, f, i, a...) m(n, ## a); _REPEAT_7(m, f(n, i), f, i, ## a)
#define _REPEAT_9(m, n, f, i, a...) m(n, ## a); _REPEAT_8(m, f(n, i), f, i, ## a)
#define _REPEAT_10(m, n, f, i, a...) m(n, ## a); _REPEAT_9(m, f(n, i), f, i, ## a)

#define _REPEAT_ADD_ONE_0 1
#define _REPEAT_ADD_ONE_1 2
#define _REPEAT_ADD_ONE_2 3
#define _REPEAT_ADD_ONE_3 4
#define _REPEAT_ADD_ONE_4 5
#define _REPEAT_ADD_ONE_5 6
#define _REPEAT_ADD_ONE_6 7
#define _REPEAT_ADD_ONE_7 8
#define _REPEAT_ADD_ONE_8 9
#define _REPEAT_ADD_ONE_9 10
#define _REPEAT_ADD_ONE_10 11

#define _REPEAT_ADD_0(x) x
#define _REPEAT_ADD_1(x) _REPEAT_ADD_ONE(x)
#define _REPEAT_ADD_2(x) _REPEAT_ADD_1(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_3(x) _REPEAT_ADD_2(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_4(x) _REPEAT_ADD_3(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_5(x) _REPEAT_ADD_4(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_6(x) _REPEAT_ADD_5(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_7(x) _REPEAT_ADD_6(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_8(x) _REPEAT_ADD_7(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_9(x) _REPEAT_ADD_8(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_10(x) _REPEAT_ADD_9(_REPEAT_ADD_ONE(x))

sample.c:

#include "repeat.h"

#define INNER_MACRO(inner, outer) if (inner == outer) printf("Match\n")
#define INNER_BLOCK  { if (inner == outer) printf("Match\n"); }

#define OUTER_MACRO_INNER_MACRO(outer) REPEAT_ADD_ONE(INNER_MACRO, 3, 0, outer)
#define OUTER_BLOCK_INNER_MACRO { REPEAT_ADD_ONE(INNER_MACRO, 3, 0, outer); }
#define OUTER_MACRO_INNER_BLOCK(outer) REPEAT_ADD_ONE(INNER_BLOCK, 3, 0, outer)
#define OUTER_BLOCK_INNER_BLOCK { REPEAT_ADD_ONE(INNER_BLOCK, 3, 0, outer); }

void outer_macro_inner_macro() {
    REPEAT_ADD_ONE(OUTER_MACRO_INNER_MACRO, 2, 1);
}

void outer_macro_inner_block() {
    REPEAT_ADD_ONE(OUTER_MACRO_INNER_BLOCK, 2, 1);
}

void outer_block_inner_macro() {
    REPEAT_ADD_ONE(OUTER_BLOCK_INNER_MACRO, 2, 1);
}

void outer_block_inner_block() {
    REPEAT_ADD_ONE(OUTER_BLOCK_INNER_BLOCK, 2, 1);
}

sample.c中,我展示了四种接近我想要的变体。但是没有一个完全符合要求。这是我使用"cpp sample.c > out.c; astyle out.c;"命令输出的结果。
void outer_macro_inner_macro() {
    REPEAT_ADD_ONE(INNER_MACRO, 3, 0, 1);
    REPEAT_ADD_ONE(INNER_MACRO, 3, 0, 2);
}

void outer_macro_inner_block() {
    REPEAT_ADD_ONE({ if (inner == outer) printf("Match\n"); }, 3, 0, 1);
    REPEAT_ADD_ONE({ if (inner == outer) printf("Match\n"); }, 3, 0, 2);
}

void outer_block_inner_macro() {
    {
        if (0 == outer) printf("Match\n");
        if (1 == outer) printf("Match\n");
        if (2 == outer) printf("Match\n");
    }(1);
    {
        if (0 == outer) printf("Match\n");
        if (1 == outer) printf("Match\n");
        if (2 == outer) printf("Match\n");
    }(2);
}

void outer_block_inner_block() {
    { {
            if (inner == outer) printf("Match\n");
        }(0, outer);
        {
            if (inner == outer) printf("Match\n");
        }(1, outer);
        {
            if (inner == outer) printf("Match\n");
        }(2, outer);
    }(1);
    { {
            if (inner == outer) printf("Match\n");
        }(0, outer);
        {
            if (inner == outer) printf("Match\n");
        }(1, outer);
        {
            if (inner == outer) printf("Match\n");
        }(2, outer);
    }(2);
}

这里是我想要得到的输出:

void desired_results() {
   {
       if (0 == 1) printf("Match\n");
       if (1 == 1) printf("Match\n");
       if (2 == 1) printf("Match\n");
   };
   {
       if (0 == 2) printf("Match\n");
       if (1 == 2) printf("Match\n");
       if (2 == 2) printf("Match\n");
   };
}

实际上,如果我将块用作外层循环体,则可以使事情正常运行,但如果我使用类似函数的宏,则无法正常工作。但我需要使用带有参数的宏,以便循环体可以将循环计数器用作常量而不是变量。

“宏”-“宏”的问题在于对REPEAT_ADD_ONE()的内部递归调用没有展开。答案似乎是推迟内部循环的扩展,直到创建外部循环,然后强制进行另一次通过以扩展内部循环。但由于某种原因,我的“随机猴子”方法尚未产生解决方案……

[1]故意轻描淡写。
4个回答

4

在这里得到的帮助(并学习 P99ChaosOrderCloak),我想我有一个相当简单和紧凑的概念验证(1)。由于我只想要“重复”功能而不是一个完整的解释器,所以我采用了一种与其他解决方案有些不同的方法。我没有创建通用的“if”,“while”或“when”宏,而是直接使用了一系列的“递减”宏,这些宏扩展为所需的宏加上对于n-1的宏调用。

#ifndef _REPEAT_H
#define _REPEAT_H

// Usage: REPEAT_ADD_ONE(macro, times, start_n, macro_args... )
//        Recursion allowed if inner macros use REPEAT_ADD_ONE_INNER().
//        This demo header only allows 3 layers of recursion and max n=10.
//        Sample code at bottom.

#define REPEAT_ADD_ONE(macro, times, start_n, macro_args... )           \
    _REPEAT_EXPAND_3(REPEAT_ADD_ONE_INNER(macro, times, start_n, ## macro_args))

#define REPEAT_ADD_ONE_INNER(macro, times, start_n, macro_args... )     \
    _REPEAT_ ## times(macro, start_n, _REPEAT_ADD_ONE, ## macro_args)

#define _REPEAT_0(args...)  /* empty */
#define _REPEAT_1(macro, n, func, args...) _REPEAT_DEFER(macro)(n, ## args)
#define _REPEAT_2(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_1(m, f(n), f, ## a)
#define _REPEAT_3(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_2(m, f(n), f, ## a)
#define _REPEAT_4(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_3(m, f(n), f, ## a)
#define _REPEAT_5(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_4(m, f(n), f, ## a)
#define _REPEAT_6(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_5(m, f(n), f, ## a)
#define _REPEAT_7(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_6(m, f(n), f, ## a)
#define _REPEAT_8(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_7(m, f(n), f, ## a)
#define _REPEAT_9(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_8(m, f(n), f, ## a)
#define _REPEAT_10(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_9(m, f(n), f, ## a)
// ...

#define _REPEAT_ADD_ONE(n, ignore...) _REPEAT_ADD_ONE_ ## n
#define _REPEAT_ADD_ONE_0 1
#define _REPEAT_ADD_ONE_1 2
#define _REPEAT_ADD_ONE_2 3
#define _REPEAT_ADD_ONE_3 4
#define _REPEAT_ADD_ONE_4 5
#define _REPEAT_ADD_ONE_5 6
#define _REPEAT_ADD_ONE_6 7
#define _REPEAT_ADD_ONE_7 8
#define _REPEAT_ADD_ONE_8 9
#define _REPEAT_ADD_ONE_9 10
#define _REPEAT_ADD_ONE_10 11
// ...

#define _REPEAT_EMPTY()
#define _REPEAT_DEFER(token) token _REPEAT_EMPTY()

#define _REPEAT_EXPAND_3(args...) _REPEAT_EXPAND(_REPEAT_EXPAND(_REPEAT_EXPAND(args)))
#define _REPEAT_EXPAND(args...) args
// ...

#endif // _REPEAT_H

#ifdef SAMPLE_CODE
// to generate code:   cpp -DSAMPLE_CODE sample.c 
// or easier to read:  cpp -DSAMPLE_CODE sample.c > out.c; astyle out.c; less out.c
// to compile and run: gcc  -Wall -O3 -DSAMPLE_CODE sample.c -o sample

int printf(const char *format, ...);

#define BODY(i) printf("%d\n", i);
void simple(void) {
    REPEAT_ADD_ONE(BODY, 5, 1);
}

#define INNER(k, j, i) \
    printf("(%d, %d, %d)\n", i, j, k);          \
    if (i == j && j == k) printf("Match!\n")
#define MIDDLE(j, i) REPEAT_ADD_ONE_INNER(INNER, 2, 2, j, i)
#define OUTER(i) REPEAT_ADD_ONE_INNER(MIDDLE, 3, 0, i)
void recursive(void) {
    REPEAT_ADD_ONE(OUTER, 2, 1);
}

int main() {
    simple();
    recursive();
    return 0;
}

#endif // SAMPLE_CODE 

我仍然难以理解许多微妙之处,但正如其他人指出的那样,一般规则是没有宏可以扩展自身。解决这个问题的方法是创建一个宏,只扩展到它调用自身的点,然后在这个结果周围放置一个包装器来完成扩展。
我最终使用的(常见)技巧是利用函数类型宏只有紧接着括号时才会扩展的事实。可以使用一个“defer”宏,在调用宏名称和其括号之间放置一个“空”标记,然后将其作为另一个宏的参数进行“扩展”。
由于参数扩展发生在与初始扩展不同的上下文中,因此初始宏将再次扩展。在我的解决方案中,每个潜在递归级别需要一层扩展。如果要操作代码以理解它,减少扩展次数以检查中间结果可能很有用。
感谢所有的帮助!
(1)确实,对于递归预处理器宏,“相当简单”的标准非常宽松。尽管如此,它相当紧凑。

4
Vesa Karvonen的"Order"库/语言绝对可以帮你做到这一点。它在C预处理器中实现了无限制的递归和循环,并且作为一个非常酷的奖励,还用“正确”的编程语言的简洁语法来装扮它(澄清一下:这不是替代预处理器,它只是做了很多标记粘贴以保持关键字的短小。它仍然是纯CPP)。
它使用一种相当不同的技术,将您的元程序转换为CPS,然后将它们传递给一个单一的循环结构,该结构具有潜在的数万亿个步骤,并且以严格线性的方式执行元程序。因此,循环和递归函数可以嵌套得很深,因为它们没有需要交互和涂成蓝色的单独驱动程序。
是的,真的,有人使用CPP宏实现了完整的虚拟机和解释器。它让人感到害怕。
(编辑:如果Rosetta Code对您也停止工作,请尝试存档版本。)

感谢提供指针。源代码似乎也可以在Mensonides的Chaos库旁边找到:http://chaos-pp.cvs.sourceforge.net/viewvc/chaos-pp/ - Nathan Kurz

2

P99 可能会提供您所需的功能。它具有多种类型的宏迭代器,如简单的 P99_UNROLLP99_SER 等和通用的 P99_FOR


嗨Jens --- 看起来很令人兴奋,我一定会去看看。文档非常好!似乎采用了基于NARGS的方法,并在可变参数上进行所有处理。您是否知道循环内部的循环是否有效,并且内部循环的宏是否可以接收外部循环的计数器作为参数? - Nathan Kurz
@NathanKurz,基于相同原语嵌套两个循环不容易实现。但是,如果对于使用情况,将P99_FORP99_SER(或类似)结合起来就可以了。这些构造中唯一具有类似循环计数器的是P99_FoR,但应该可以使其对另一个内部迭代构造可访问。 - Jens Gustedt
我已经研究了P99,但我不想依赖外部库,而且很难开始理解仅适用于P99_FOR的必要内容。所有东西都被命名得非常混乱和杂乱无章。 - MarcusJ
@MarcusJ,如果你有这样的印象并且名字让你感到困惑,我很抱歉。P99_FOR 是非常不平凡的,如果不是这样,我可能不会首先将其作为开源项目提供,而只是在我的博客上发布一些代码。我认为自己实现这样的东西通常不是一个好主意,它非常耗时且容易出错。 - Jens Gustedt

1

我不确定我理解你的所有宏。这个答案在这里(现在也在这里)解释了如何创建一个通用的REPEAT宏,就像这样:

#define REPEAT(count, macro, ...) \
    WHEN(count) \
    ( \
        OBSTRUCT(REPEAT_INDIRECT) () \
        ( \
            DEC(count), macro, __VA_ARGS__ \
        ) \
        OBSTRUCT(macro) \
        ( \
            DEC(count), __VA_ARGS__ \
        ) \
    )
#define REPEAT_INDIRECT() REPEAT

这需要一个计数器、一个宏和用户数据。由于传入的宏是延迟的,因此可以直接递归调用REPEAT宏。因此,以下是您的OUTERINNER重复宏:
#define OUTER(i, j) { REPEAT(j, INNER, i) }
#define INNER(j, i) if (j == INC(i)) printf("Match\n");

EVAL(REPEAT(2, OUTER, 3))

这将输出以下内容:
{ 
    if (0 == 1) printf("Match\n"); 
    if (1 == 1) printf("Match\n"); 
    if (2 == 1) printf("Match\n"); 
}
{
    if (0 == 2) printf("Match\n"); 
    if (1 == 2) printf("Match\n"); 
    if (2 == 2) printf("Match\n");
}

希望这有意义。

嗨,保罗---非常感谢您的回答,也感谢您提供的工作示例。我已经多次阅读了您的页面和其他答案。虽然我逐行理解了它,但我仍然对扩展如何工作以及间接引用如何与(双重)延迟交互感到困惑。但在您的示例和Jen的P99代码之间,我希望能够弄清楚所有这些。您还发现其他有用的资源吗? - Nathan Kurz
@NathanKurz 间接寻址是必要的,以避免获得一个被涂成蓝色的标记。尽管延迟宏没有被评估,但它由预处理器扫描,并在其禁用上下文中看到一个标记(比如再说一次“REPEAT”)时,它将把其涂成蓝色,而该标记不再扩展。因此,我们在那里放了一个REPEAT_INDIRECT宏,这样预处理器就不会看到REPEAT标记,直到应用另一个扫描,希望它没有REPEAT - Paul Fultz II
此外,OBSTRUCT(或双重延迟)只是意味着需要两次扫描才能完全展开宏。因此,EXPAND(OBSTRUCT(macro))()等同于DEFER(macro)()。由于条件限制,这对于REPEAT是必要的。WHEN宏将对其应用一次扫描。基本上,导致OBSTRUCT(REPEAT_INDIRECT) ()变成DEFER(REPEAT_INDIRECT) ()。现在,如果我们只有DEFER(REPEAT_INDIRECT) (),那么在应用扫描后,它将变成REPEAT,导致REPEAT变成蓝色。这有意义吗? - Paul Fultz II
@NathanKurz 我所知道的唯一资源是Paul Mensonides的Chaos预处理器库。他发明了大部分这些技术,但它们更为复杂。例如,递归宏使用递归状态,因此只应用所需的扫描次数,这比EVAL宏更有效,后者即使不需要也会始终应用大量扫描。 - Paul Fultz II

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接