C 定义任何非零值

The C define with any value for non zero

在 C 中我有这样的定义:

#define VALUE 5

但用户也可以设置一个功能:

#define VALUE get_value()

由于历史原因

#define VALUE 0

表示“未设置值,使用默认值”

问题是如何编写 #if 来决定 VALUE 是否为 0

#if VALUE == 0
 ...
#else
 ...
#endif

GCC 出现 "missing binary operator before token "("" 错误。


编辑: 为了使用例更清楚:

#if VALUE == 0
  set_default_value();
#else
  set_value(VALUE)
#endif

所以我不需要 VALUE#if 中进行评估,只需看看它是否真的是“0”。

预处理器无法计算 C 函数,例如 get_value()。它只能处理静态数据,因为它在编译之前被代码替换,所以你的语句不会在运行时执行。

if (VALUE == 0) 在你的 C 代码中会在编译之前被替换为 if (get_value() == 0)。预处理器无法评估 get_value() 的 return 值(即使它总是 return 0)。

A #define VALUE 5 行使 C 预处理器将字符串 VALUE 替换为字符串 5 ,无论它在哪里(即“字符串”之外)。生成的程序甚至不再包含 VALUE,这就是编译器本身所看到的(1).

做个实验:获取你的程序(将其缩减为几行),然后 运行:

cc -E proggie.c > proggie.i

在 Unixy 系统下,您将获得一个包含预处理 C 的文件 proggie.i,这是编译器本身所看到的。看不到 VALUE,尝试在源代码中设置 VALUE 最终会尝试设置 0,这显然行不通。

(1) 从历史上看,C 编译器 运行 源代码上的程序链(然后是以 KiB 而非 GiB 衡量的内存),第一个 --preprocessor-- 通常被称为 cpp;今天的编译器通常不再有单独的预处理器。但从概念上讲,第一步仍然是预处理代码(处理 #include#if#defined 宏)。

您可以使用预处理模式匹配。

#define SECOND(...) SECOND_I(__VA_ARGS__,,)
#define SECOND_I(A,B,...) B

#define GLUE(A,B) GLUE_I(A, B)
#define GLUE_I(A,B) A##B

#define ZERO_TEST(X_) SECOND(GLUE(ZERO_TEST_AGAINST_,X_),0)
#define ZERO_TEST_AGAINST_0 ,1

这里的关键构造是 SECOND 宏,它间接扩展到它的第二个参数。对于模式匹配,您可以通过仔细构造第一个参数来使用它;因为 SECOND 通常扩展到它的第二个参数,所以无论你构造什么,通常都会被忽略。但是由于 SECOND 间接 扩展到它的第二个参数 ,你可以通过在特定情况下用逗号扩展第一个参数来挑选特定的模式,这将推入一个新的第二个参数。

在这种情况下,我们在 ZERO_TEST_AGAINST_ 的末尾有一个间接粘贴,我们正在寻找其结果为 ZERO_TEST_AGAINST_0

要使用这个:

#if ZERO_TEST(VALUE)
  set_default_value();
#else
  set_value(VALUE)
#endif

演示

http://coliru.stacked-crooked.com/a/2a6afc189637cfd3

警告

这完全符合您给定的规格;如果您有括号定义,则间接粘贴不适用于此表单:

#define VALUE (5)

...或:

#define VALUE (get_value() << 2) | 1

...因为 ZERO_TEST_AGAINST_( 不加入以生成有效令牌。

一个通用的解决方案可能是不可能的,但你可以破解一些东西:

#define CAT(x, ...) CAT_(x, __VA_ARGS__)
#define CAT_(x, ...) x##__VA_ARGS__

#define CHECK_VALUE_CHECK_0 )(

#define CHECK_VALUE_FALSE(...) CHECK_VALUE_TRUE
#define CHECK_VALUE_TRUE() 1

#define CHECK_VALUE CHECK_VALUE_(CAT(CHECK_VALUE_CHECK_, VALUE))
#define CHECK_VALUE_(...) CHECK_VALUE_FALSE(__VA_ARGS__)

#if CHECK_VALUE
#error Value is 0.
#else
#error Value is not 0.
#endif

现在,如果 VALUE 定义为 0,宏 CHECK_VALUE 将扩展为 1。否则它将扩展为 CHECK_VALUE_TRUE,这是一个未知的标识符,被 #if.

认为是错误的

这个解决方案是 hacky:

  • 如果 VALUE0, 开头,则会导致硬错误。
  • 如果 VALUE 以字母或数字或 _ 以外的其他内容开头(例如 (),则会导致硬错误。
  • ...

我认为你误解了历史 'the value is not set' 的工作原理。

在 C 预处理器中,只要你在表达式中有一个 #if 指令 any 标识符(并且不是特殊函数 defined) 将在计算 #if 之前被常量 0 替换。所以以你的例子

#define VALUE get_value()
#if VALUE == 0

扩展了 VALUE 宏,然后,由于没有定义 get_value 宏,它被替换为 0,留下

#if 0() == 0

这会给出您看到的语法错误。