【问题标题】:Why can't we use the preprocessor to create custom-delimited strings?为什么我们不能使用预处理器来创建自定义分隔的字符串?
【发布时间】:2013-05-23 19:49:18
【问题描述】:

我正在玩弄 C 预处理器,但似乎很简单的事情却失败了:

#define STR_START "
#define STR_END "

int puts(const char *);

int main() {
    puts(STR_START hello world STR_END);
}

当我用 gcc 编译它时(注意:与 clang 类似的错误),它失败了,出现以下错误:

$ gcc test.c test.c:1:19:警告:缺少终止“字符 test.c:2:17:警告:缺少终止“字符 test.c:在函数“main”中: test.c:7:错误:缺少终止“字符 test.c:7: error: ‘hello’ undeclared (第一次在这个函数中使用) test.c:7: 错误:(每个未声明的标识符只报告一次 test.c:7:错误:对于它出现的每个函数。) test.c:7:错误:“世界”之前的预期“)” test.c:7:错误:缺少终止“字符

哪一种让我很困惑,所以我通过预处理器运行它:

$ gcc -E test.c # 1 “测试.c” #1“” #1“” # 1 “测试.c” test.c:1:19:警告:缺少终止“字符 test.c:2:17:警告:缺少终止“字符 int puts(const char *); int main() { puts("你好世界"); }

尽管有警告,但生成完全有效的代码(在粗体文本中)!

如果 C 中的宏只是文本替换,为什么我的初始示例会失败?这是编译器错误吗?如果没有,标准中哪里有与这种情况有关的信息?

注意:我不是在寻找如何使我的初始 sn-p 编译。我只是在寻找有关此方案失败原因的信息。

【问题讨论】:

  • 您的问题标题有些错误。您可以使用预处理器创建自定义字符串。只是您这样做的尝试是错误的。为此有一个特殊的运算符,#,称为字符串化。
  • @JensGustedt 不,这不是自定义分隔字符串,只是来自参数的字符串。回滚。

标签: c gcc macros clang c-preprocessor


【解决方案1】:

问题在于,即使代码扩展为" hello, world ",预处理器也不会将其识别为单个字符串文字标记;相反,它被识别为标记"hello,world" 的(无效)序列

N1570:

6.4 词法元素
...
3 token 是语言在翻译阶段 7 和 8 中的最小词汇元素。 标记的类别是:关键字、标识符、常量、字符串文字和标点符号。 预处理标记是翻译中语言的最小词汇元素 阶段 3 到 6。预处理标记的类别是:标头名称, 标识符、预处理数字、字符常量、字符串文字、标点符号和 在词法上不匹配其他预处理的单个非空白字符 标记类别。69)如果 '" 字符与最后一个类别匹配,则行为是 未定义。预处理标记可以用空格分隔;这包括 cmets(稍后描述)或空白字符(空格、水平制表符、换行符、 垂直制表符和换页),或两者兼而有之。如 6.10 所述,在某些情况下 在翻译阶段 4 中,空格(或没有空格)的作用超过 预处理令牌分离。空白可能出现在预处理令牌中 仅作为标题名称的一部分或字符常量中的引号字符之间 或字符串文字。
69) 在翻译阶段 4 内部使用了一个额外的类别,placemarkers(见 6.10.3.3);这不可以 出现在源文件中。

请注意,'" 在此定义下都不是标点符号。

【讨论】:

  • 非常有趣。老实说,我很惊讶编译器接受这一点,看看 gcc 对 UB 的宽容程度。
  • @RichardJ.RossIII,C 的词法分析阶段非常严格。 C 编译器不接受违反语法规则。这些是需要诊断的约束违规。然后,不要忘记字符串化有一个特殊的运算符#,所以不需要这样的构造。
  • 这很奇怪。事实上,传统的 C 编译器会接受这一点,因为它在预处理器步骤之后会被 relexed。话又说回来,传统的 C 曾经在字符串常量中扩展宏,这几乎被普遍认为是不好的。
【解决方案2】:

预处理器在multiple phases 中运行。阶段 3,标记化,发生在扩展之前,因此预处理器宏必须代表完整的标记。在您的情况下,STR_STARTSTR_END 被标记然后替换,这使得这些标记无效。

【讨论】:

    【解决方案3】:

    这里

    #define STR_START "
    

    编译器需要 字符串字面量。字符串文字必须以右引号结尾。这就是编译器抱怨缺少终止 " 字符的原因。

    宏扩展后编译器再次报错,因为令牌无效。


    例如,MSVC 编译器报错:

    error C2001: newline in constant
    

    扩展后它抱怨缺少引号。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-01
      • 2020-01-31
      • 2021-08-03
      • 2018-01-27
      • 1970-01-01
      • 2012-06-03
      • 2018-11-16
      相关资源
      最近更新 更多