【问题标题】:How to get Xcode 8 C preprocessor to ignore // comments in #defines如何让 Xcode 8 C 预处理器忽略 #defines 中的 // 注释
【发布时间】:2017-05-27 00:08:26
【问题描述】:

C 预处理器 (cpp) 似乎应该正确处理此代码:

#define A 1 // hello there

int foo[A];

我希望将A 替换为1

发生的情况是A1 // 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,所以我怀疑这是由于编译器过时造成的。

【问题讨论】:

  • 我认为预处理器对 cme​​ts 一无所知。为什么不直接使用 /* */ 块评论?
  • 我无法重现此内容 (ideone.com/hfQunc)。你用的是什么编译器?
  • 请注意,// 不是有效的 ISO C 注释,它是在 C99 中引入的。确保您使用 C99 标准进行编译(和预处理)。
  • 你是如何调用你的预处理器的?
  • @LưuVĩnhPhúc 评论不参与预处理。我们在这里看到的是从预处理器获取文本输出的工件,这是标准之外的东西。

标签: c xcode macos c-preprocessor


【解决方案1】:

令我惊讶的是,我可以在我的 Mac (macOS Sierra 10.12.2;Apple LLVM version 8.0.0 (clang-800.0.42.1)) 上使用 XCode cpp /usr/bin/cpp 重现该问题 — 但不能使用 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。有一些关于如何完成的问题的答案(但它不适合胆小的人)。

【讨论】:

  • 我可以使用/usr/bin/cpp -std=c99 进行复制,但我不能使用clang -E -std=c99。我怀疑/usr/bin/cpp 正在做一些奇怪的向后兼容的事情。
  • 有趣的是,即使clang --driver-mode=cpp test.c 也做了正确的事情。 Apple 的 cpp 包装有什么问题吗?
  • @Schwern:是的——我无法弄清楚到底发生了什么,但cpp 似乎有一个盲点。好奇——相当于一个错误。如果它不尊重-std=c99,它应该抱怨它,而不是默默接受而是忽略它。
  • 好吧,这似乎已经缩小到 Xcode 错误(已报告给 Apple)——问题/标题是否应该相应更新?
  • @machinaut:可能值得将标题更新为“如何让 XCode 8 独立 C 预处理器识别 // cmets?” (因为/* … */ 也是单行注释,或者至少是单行注释)。在有限的时间内应该是一个问题——直到 Apple 解决问题——所以标题中的版本是合理的(如果你愿意,你可以使用 8.2 或 8.2.1)。我没有安装旧版本的 XCode,所以我不能向后测试;在旧版本中也可能是一个问题。添加osx 和/或xcode 标签可能是个好主意。
【解决方案2】:

请注意,// 不是有效的 C90 注释。它是在 C99 中引入的,因此请确保您的编译器和预处理器知道它们将使用 C99 标准。在许多情况下是-std=c99。 (这个问题已经被编辑清楚了)


接下来是我不相信预处理器关心 cmets。从 C99 规范的 6.10 开始,它显示了预处理器指令的语法,并且没有提到 cmets...

ANSI C 标准明确说明 cmets 应该在 2.1.1.2“翻译阶段”第 3 阶段(C99 中的 5.1.1.2)中被替换。 (取自this other answer)。

  1. 源文件被分解为预处理标记和空白字符序列(包括 cmets)。源文件不应以部分预处理标记或部分注释结尾。 每条评论都被一个空格字符替换。 换行符被保留。除换行符之外的每个非空空白字符序列是保留还是替换为一个空格字符是由实现定义的。

较旧的工具可能没有遵循这一点,因为它们早于任何 C 标准,或者它们有错误,或者它们对标准的解释不同。他们可能保留了这些错误/怪癖以实现向后兼容性。使用clang -E -std=c99/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 "<built-in>" 1
# 1 "<built-in>" 3
# 330 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "test.c" 2


int foo[1 // hello there];

$ /usr/bin/clang -E -std=c99 test.c
# 1 "test.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 331 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "test.c" 2


int foo[1];

我怀疑以/usr/bin/cpp 调用clang 会导致与cpp 的原始行为在行为不清楚时建立的错误/怪癖兼容。

我想这里的教训是使用cc -E 而不是cpp 来确保行为一致。

【讨论】:

  • 1. OP 说“即使使用cpp -std=c99 test.c 也会重现”。 2.预处理器是C的分词器。它必须关心 cmets,否则它无法完成它的工作。
  • @melpomene 1. 我在写答案时编辑了问题。至于 2...虽然预处理器必须关心普通 C 代码中的 cmets,但它并没有说明宏定义中的 cmets。我在规范中没有看到任何必须这样做的内容。 6.4.9.2 可以解释为它适用于任何地方,甚至是宏,但我不确定,因为 6.4.9.3 有一个 #include "//e" // undefined behavior 的例子。
  • 是的,确认我编辑添加-std=c99doesn't修复它。对飞行中的碰撞感到抱歉。我应该为此做些什么特别的事情?
  • @machinaut 不,这只是发生的事情。
【解决方案3】:

来自 C11 规范(已添加重点):

5.1.1.2 翻译阶段

翻译的语法规则之间的优先级由 以下阶段6).

  1. [...] 多字节字符被映射 [...] 到源字符集 [...] 三元字符序列被替换 [...]

  2. 每个反斜杠字符 () 的实例紧跟一个换行符都被删除,拼接物理源代码行 [...]

  3. 源文件被分解为预处理标记和空白字符序列(包括 cmets)。 [...] 每条评论都替换为一个空格字符。 [...]

  4. 执行预处理指令,扩展宏调用,并执行 _Pragma 一元运算符表达式。 [...]

注释 6) 指出:

实现应该表现得好像这些单独的阶段发生了一样,即使 尽管许多通常在实践中折叠在一起。源文件, 翻译单元,而翻译的翻译单元不需要 必须存储为文件,也不需要任何一对一 这些实体与任何外部表示之间的对应关系。 描述只是概念性的,并没有具体说明 具体实现。

因此,符合 C11 规范的实现不需要单独的预处理器。这意味着cpp 命令可以为所欲为。并且允许编译器驱动程序以任何它想要的方式执行阶段 1 到 3。所以预处理后获取输出的正确方法是使用cc -E调用编译器驱动。

【讨论】:

  • 预处理器完成以上所有工作:gcc.gnu.org/onlinedocs/cpp/Initial-processing.html
  • @melpomene 对于 gcc 可能是这样,但这不是必需的,并且鉴于 OP 是 “使用最新 Xcode 附带的 cpp”,他几乎可以肯定 不使用 gcc。
  • 我认为这提供了为什么cppcc -E 行为不同的最后一块拼图。
  • 不过,似乎有一个完善的秩序。注释是整个语言的空白标记。无论涉及多少工具或步骤,C99 编译器都不应使用注释扩展宏。唯一可能的解释是:1)编译器中的错误; 2) 以前的 C 标准以不同的方式处理这个问题。
  • @giusti 您错过了 OP 没有运行编译器这一点。他运行了一个名为cpp 的未记录命令。他应该做的是用cc -E运行编译器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-04
相关资源
最近更新 更多