c-preprocessor - C99 预处理器图灵完整吗?

标签 c-preprocessor theory boost-preprocessor turing-complete

发现 Boost preprocessor's capabilities 后我发现自己在想:C99 预处理器图灵完整吗?

如果没有,缺少什么不符合资格?

最佳答案

宏不会直接递归扩展,但我们可以通过一些方法来解决这个问题。

在预处理器中执行递归的最简单方法是使用延迟表达式。延迟表达式是需要更多扫描才能完全展开的表达式:

#define EMPTY()
#define DEFER(id) id EMPTY()
#define OBSTRUCT(...) __VA_ARGS__ 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__

现在,如果我们想使用递归实现 REPEAT 宏,首先我们需要一些递增和递减运算符来处理状态:

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

#define INC(x) PRIMITIVE_CAT(INC_, x)
#define INC_0 1
#define INC_1 2
#define INC_2 3
#define INC_3 4
#define INC_4 5
#define INC_5 6
#define INC_6 7
#define INC_7 8
#define INC_8 9
#define INC_9 9

#define DEC(x) PRIMITIVE_CAT(DEC_, x)
#define DEC_0 0
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4
#define DEC_6 5
#define DEC_7 6
#define DEC_8 7
#define DEC_9 8

接下来我们需要更多的宏来执行逻辑:

#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))

#define EAT(...)
#define EXPAND(...) __VA_ARGS__
#define WHEN(c) IF(c)(EXPAND, EAT)

现在有了所有这些宏,我们就可以编写一个递归REPEAT宏。我们使用 REPEAT_INDIRECT 宏来递归地引用自身。这可以防止宏被涂成蓝色,因为它将在不同的扫描上扩展(并使用不同的禁用上下文)。我们在这里使用OBSTRUCT,这将推迟两次扩展。这是必要的,因为条件 WHEN 已经应用了一次扫描。

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

//An example of using this macro
#define M(i, _) i
EVAL(REPEAT(8, M, ~)) // 0 1 2 3 4 5 6 7

现在,由于计数器的限制,此示例仅限于 10 次重复。就像计算机中的重复计数器会受到有限内存的限制一样。可以将多个重复计数器组合在一起来解决此限制,就像在计算机中一样。此外,我们可以定义一个 FOREVER 宏:

#define FOREVER() \
    ? \
    DEFER(FOREVER_INDIRECT) () ()
#define FOREVER_INDIRECT() FOREVER
// Outputs question marks forever
EVAL(FOREVER())

这将尝试永远输出 ?,但最终会停止,因为没有更多的扫描被应用。现在的问题是,如果我们给它无限次扫描,这个算法能否完成?这被称为停机问题,图灵完备性对于证明停机问题的不可判定性是必要的。正如您所看到的,预处理器可以充当图灵完整语言,但它不受计算机有限内存的限制,而是受到所应用扫描的有限数量的限制。

关于c-preprocessor - C99 预处理器图灵完整吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3136686/

相关文章:

c++ - 条件编译困惑和失败

c - 如何在不修改头文件的情况下取消应用程序代码中的宏定义?

java - 通过访问方法引用属性还是直接在父类(super class)中引用属性?

确定不同长度变化的算法

c++ - 使用 Boost.Preprocessor 来减少代码重复

c++ - 为什么这个 PP_ARG_COUNT 宏需要一个 PP_EXPAND?

c - 旧的 C 编译器在 #ifndef#define 上阻塞

添加到类型名称的 C 宏

java - 为什么 Java 既有检查异常也有未检查异常?

c++ - 使用 Boost 预处理器将 Any 提升到 Boost Variant