10

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

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

4

3 回答 3

10

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

N1570

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

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

于 2013-05-28T18:52:25.743 回答
8

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

于 2013-05-28T18:57:24.443 回答
0

这里

#define STR_START "

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

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


例如,MSVC 编译器会抱怨:

error C2001: newline in constant

扩展后它抱怨缺少引号。

于 2013-05-28T20:20:16.020 回答