将 C 预处理器中的预处理数限制为仅处理有效的浮点数和整数常量

Restricting preprocessing-numbers in a C preprocessor to only handle valid floating and integer constants

我目前正在实现 C11 编译器,我的目标是将预处理器集成到其余编译器中,而不是将其作为独立组件。因此,预处理器可以安全地假设其输出在以下阶段有效。

阅读有关预处理数字标记的信息,它似乎只是为了简化独立预处理器的实现而存在。简化数字格式,它不必处理数字表达式的全部复杂性。引用 GCC docs:

The purpose of this unusual definition is to isolate the preprocessor from the full complexity of numeric constants. It does not have to distinguish between lexically valid and invalid floating-point numbers, which is complicated.

由于预处理器将集成到编译器框架的其余部分,这对我来说不是问题。

在第 6.4.8.4 节中[预处理数字;语义]的C11标准,它声称

A preprocessing number does not have type or a value; it acquires both after a successful conversion (as part of translation phase 7) to a floating constant token or an integer constant token.

所以似乎每个预处理数字都会在稍后的编译过程中转换为浮点数或整数常量。我在标准中找不到任何其他对预处理数字的引用,所以这似乎是它们的唯一目的,但我可能错了。

我的问题是,预处理器将预处理数字限制为仅有效的整数和浮点常量是否有效?或者是否存在这样的限制会导致其他有效程序失败的情况?

肯定有一些有效程序包含不可转换为整数或浮点数的 pp 数字。常见的情况是预处理令牌不会成为令牌。

例如,它可能被字符串化:


#define STRINGIFY_(X) #X
#define STRINGIFY(V)  STRINGIFY_(V)
#define VERSION 3.4.6a
#define PROGNAME foo

int main(void) {
  printf("%s-%s\n", STRINGIFY(PROGNAME), STRINGIFY(VERSION));
}

此外,上面示例中的版本号可以通过标记连接生成,另一种预处理标记永远不会成为程序标记的方式:


#include <stdio.h>
#define STRINGIFY_(X) #X
#define STRINGIFY(V)  STRINGIFY_(V)
#define CONCAT3_(x,y,z) x##y##z
#define CONCAT3(x,y,z) CONCAT3_(x,y,z)
#define CONCAT_V(mj, mn, pl) CONCAT3(mj, ., CONCAT3(mn, ., pl))

#define MAJOR 3
#define MINOR 4
#define PATCH 6a

#define VERSION CONCAT_V(MAJOR, MINOR, PATCH)
#define PROGNAME foo

int main(void) {
  printf("%s-%s\n", STRINGIFY(PROGNAME), STRINGIFY(VERSION));
}

还有其他方法可以使 pp-number(或任何其他预处理令牌)永远不会转换为令牌:

  1. 作为在其替换文本中不使用相应参数的宏的参数。

  2. 在预处理器条件的程序文本中,其控制表达式为 false。

    这经常被“在野外”用来隐藏 #if 0#endif 块中未完全编写的代码;被排除的代码可能有几乎任意的语法错误,只要注释和字符串被终止,包括无效的 pp-numbers 甚至杂散的标点符号。 (@ 是无法转换为令牌的有效预处理令牌。)