如何让Xcode 8 C预处理器忽略// #defines中的注释
C预处理器( cpp
)似乎应该正确处理此代码:
#define A 1 // hello there int foo[A];
我希望将A
替换为1
。
会发生什么是A
替换为1 // hello there
,这导致cpp -std=c99 test.c
的以下输出:
# 1 "test.c" int foo[1 // hello there];
哪个是无效的C并且无法编译。
如何让cpp
执行正确的替换?
关于编译器的注意事项:在mac上使用最新的(8.2.1,2016年12月)Xcode中的cpp
,所以我怀疑它是由于过时的编译器造成的。
令我惊讶的是,我可以使用/usr/bin/cpp
(即XCode cpp
)在我的Mac(macOS Sierra 10.12.2; Apple LLVM version 8.0.0 (clang-800.0.42.1)
)上重现问题 – 但不使用GNU cpp
(我只使用cpp
调用)。
解决方法包括:
/usr/bin/gcc -E -std=c99 test.c
这使用clang
包装器gcc
来运行C预处理器并正确处理版本。 您可以添加-v
选项并查看它运行的内容; 我没有看到它运行cpp
本身(它运行clang -cc1 -E
有很多其他信息)。
您还可以使用:
clang -E -std=c99 test.c
这实际上是一回事。
您也可以安装GCC并使用它而不是XCode。 关于如何完成这项工作的答案存在问题(但不适合胆小的人)。
请注意//
不是有效的C90注释。 它是在C99中引入的,因此请确保您的编译器和预处理器知道它们将使用C99标准。 在许多人的-std=c99
。 (这个问题自编辑后就明确了)
接下来是我不相信预处理器关心评论。 从C99规范的6.10开始,它显示了预处理器指令的语法,并且没有提到注释……
ANSI C标准明确指出应该在2.1.1.2“翻译阶段”阶段3(C99中的5.1.1.2)中替换注释。 (从其他答案中得出 )。
- 源文件被分解为预处理标记和空白字符序列(包括注释)。 源文件不应以部分预处理标记或部分注释结束。 每个注释都被一个空格字符替换。 保留换行符。 是否保留或替换为新行以外的每个非空白字符序列是由实现定义的。
较旧的工具可能没有遵循这一点,因为它们先于任何C标准,或者它们有错误或者它们以不同方式解释标准。 他们可能会保留这些错误/怪癖以实现向后兼容。 用clang -E -std=c99
vs /usr/bin/cpp -std=c99
证实了这一点。 尽管它们是同一个编译器,但它们的行为却截然不同。
$ /usr/bin/cpp --version Apple LLVM version 8.0.0 (clang-800.0.42.1) Target: x86_64-apple-darwin16.3.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin $ clang --version Apple LLVM version 8.0.0 (clang-800.0.42.1) Target: x86_64-apple-darwin16.3.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin $ ls -l /usr/bin/cpp -rwxr-xr-x 1 root wheel 18240 Dec 10 01:04 /usr/bin/cpp $ ls -l /usr/bin/clang -rwxr-xr-x 1 root wheel 18240 Dec 10 01:04 /usr/bin/clang $ /usr/bin/cpp -std=c99 test.c # 1 "test.c" # 1 "" 1 # 1 " " 3 # 330 " " 3 # 1 " " 1 # 1 " " 2 # 1 "test.c" 2 int foo[1 // hello there]; $ /usr/bin/clang -E -std=c99 test.c # 1 "test.c" # 1 " " 1 # 1 " " 3 # 331 " " 3 # 1 " " 1 # 1 " " 2 # 1 "test.c" 2 int foo[1];
我怀疑调用clang是因为/usr/bin/cpp
导致bug / quirk与行为不清楚时建立的cpp
的原始行为兼容。
我想这里的教训是使用cc -E
而不是cpp
来确保一致的行为。
从C11规范(重点补充):
5.1.1.2翻译阶段
翻译语法规则中的优先级由以下阶段6)指定。
[…]多字节字符被映射到源字符集[…] Trigraph序列被替换[…]
后面紧跟一个新行字符的反斜杠字符()的每个实例都被删除,拼接物理源代码行[…]
源文件被分解为预处理标记和空白字符序列( 包括注释 )。 […] 每个评论由一个空格字符替换。 […]
执行预处理指令,扩展宏调用 ,并执行_Pragma一元运算符表达式。 […]
注6)说明:
实现应该表现为好像发生这些单独的阶段,即使许多通常在实践中折叠在一起。 源文件,翻译单元和翻译的翻译单元不一定需要存储为文件,也不需要在这些实体和任何外部表示之间存在任何一对一的对应关系。 该描述仅是概念性的,并未指定任何特定实现。
因此,符合C11规范的实现不需要具有单独的预处理器。 这意味着cpp
命令可以执行任何操作。 并且允许编译器驱动程序以其想要的任何方式执行阶段1到3。 因此,在预处理之后获取输出的正确方法是使用cc -E
调用编译器驱动程序。