采用以下示例:
#define FOO(x) bar x ## baz
FOO( )
在预处理阶段之后,根据ANSI C和C99标准,以上代码的预期输出是什么?
我通过gcc -E
和clang -E
进行了上述操作,并且都产生了等效于以下内容的输出:
bar baz
另外,如果上述经过预处理的输出被认为符合标准,那该怎么办?
#define FOO(x) x ## baz
FOO( )
通过上述修改,GCC和clang仍会产生与以下等效的输出,而不会发出任何警告或错误(即使带有-Wall -Werror
标志):
baz
我怀疑上述输出不符合标准,是因为ANSI C 9899:1990标准指出:
6.8.3.3 ##运算符
对于任何一种形式的宏定义, ## 预处理令牌都不应出现在替换列表的开头或结尾。
如果在替换列表中,参数紧跟在 ## 预处理标记之前或之后,则该参数将替换为相应参数的预处理标记序列。
好的,所以在以上两个示例中,从技术上来说 ## 都不在替换列表的开头,但是x
确实可以扩展到...一个空格?没有? ...所以 ## 运算符(至少,据我所知)正在将一个空间pp令牌(或什么都没有)连接到baz
。
此外,标准规定:
对于类对象和函数类宏调用,在重新检查替换列表以查找更多要替换的宏名称之前,替换列表中每个 ## 预处理令牌的实例(不是来自参数),然后将前面的预处理令牌与后面的预处理令牌连接起来。
因此,考虑到我上面给出的第一个示例,为什么不应该将正确的输出显示为这样?
barbaz
作为一个旁注,我确实对上面给出的第一个示例进行了如下预处理时,使GCC发出了错误:
gcc -ansi -pedantic -Wall -Werror -E ex1.c -o -
输出(有错误)是:
foo.c:2:6: error: invoking macro FOO argument 1: empty macro arguments are undefined in ISO C90 [-Werror=pedantic]
FOO( )
^
# 1 "foo.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "foo.c"
bar baz
cc1: all warnings being treated as errors
但是,该错误与 ## 运算符的行为无关,这就是问题的全部原因。尽管如此,它仍然很有趣。
还要注意的是,在使用相同的标志对文件进行预处理时,clang不会发出错误。
答案 0 :(得分:5)
好的,因此从技术上讲,在以上两个示例中,##运算符都不在替换列表的开头,
这里的“技术上”一词是多余的。两个宏的替换列表在x
之前都有##
,因此该规则不适用。
但是x确实可以扩展到...一个空格吗?什么都没有?
6.4列出了被认为是预处理令牌的内容:报头名称,标识符,pp编号,字符常量,字符串文字,标点符号和“每个不能为上述字符之一的非空白字符”。请注意,此列表中明显没有什么...空格本身。
因此,您对FOO
的论点中的空格并不重要;您的参数是一个由0个预处理标记组成的序列(即,上面列表中的0个对象)。这意味着这适用:
6.10.3.3 p2:
但是,如果参数不包含预处理令牌,则该参数将替换为地标预处理令牌。
下一段3适用于此处的结果:
将地标与非地标预处理令牌连接起来会产生非地标预处理令牌。
因此,粘贴到baz
上的 placemarker 会生成baz
。
答案 1 :(得分:0)
如果您有这个:
#define FOO(x) bar x ## baz
FOO();
您得到bar baz
。因此,FOO调用中的空间不相关。
如果您有这个:
#define FOO(x) bar ## x ## baz
FOO();
您得到barbaz
是因为您要求将所有这些粘贴在一起。
但是你写:
#define FOO(x) bar x ## baz
FOO();
您得到bar baz
是因为您要求bar
以及x和baz的粘贴。类似于#define FOO(x) bar,x ## baz
,为您提供bar,baz
。