通过标记串联创建包含通用字符名称的标识符

20
我写了这段代码,通过标记串联创建包含通用字符名称的标识符。
//#include <stdio.h>
int printf(const char*, ...);

#define CAT(a, b) a ## b

int main(void) {
    //int \u306d\u3053 = 10;
    int CAT(\u306d, \u3053) = 10;

    printf("%d\n", \u306d\u3053);
    //printf("%d\n", CAT(\u306d, \u3053));

    return 0;
}

这段代码在使用gcc 4.8.2 的 -fextended-identifiers 选项gcc 5.3.1时表现良好,但在使用clang 3.3时会出现错误信息:

prog.c:10:17: error: use of undeclared identifier 'ねこ'
        printf("%d\n", \u306d\u3053);
                       ^
1 error generated.

本地 clang(Apple LLVM版本7.0.2(clang-700.1.81))出现错误信息:

$ clang -std=c11 -Wall -Wextra -o uctest1 uctest1.c
warning: format specifies type 'int' but the argument has type
      '<dependent type>' [-Wformat]
uctest1.c:10:17: error: use of undeclared identifier 'ねこ'
        printf("%d\n", \u306d\u3053);
                       ^
1 warning and 1 error generated.

当我使用-E选项让编译器输出宏展开后的代码时,gcc 5.3.1输出了如下内容:

# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "main.c"

int printf(const char*, ...);



int main(void) {

 int \U0000306d\U00003053 = 10;

 printf("%d\n", \U0000306d\U00003053);


 return 0;
}

本地的clang生成了这个:

# 1 "uctest1.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 326 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "uctest1.c" 2

int printf(const char*, ...);



int main(void) {

 int \u306d\u3053 = 10;

 printf("%d\n", ねこ);


 return 0;
}

printf()中声明和使用的标识符在gcc的输出中匹配,但在clang的输出中不匹配。

我知道通过令牌连接创建通用字符名称会调用未定义的行为。

引自N1570 5.1.1.2翻译阶段:

如果通过令牌连接(6.10.3.3)产生与通用字符名称的语法相匹配的字符序列,则行为未定义。

我认为这个字符序列\u306d\u3053可能“与通用字符名称的语法相匹配”,因为它包含通用字符名称作为其子字符串。我还想到,“匹配”可能意味着通过连接产生的整个标记代表一个通用字符名称,因此在此代码中不会调用此未定义的行为。

阅读PRE30-C. Do not create a universal character name through concatenation,我发现一条评论说这种连接是允许的:

禁止通过连接创建新UCN。就像做

assign(\u0001,0401,a,b,4)

只是连接一些碰巧包含UCN的东西是可以的。

以及一个日志,显示像这个案例一样的代码示例(但是有4个字符)被替换为另一个代码示例

我的代码示例是否调用了某些未定义的行为(不限于通过令牌连接生成通用字符名称时调用的行为)?还是这是clang的错误?


就我而言,gcc 5.3.1 看起来更糟糕...取消注释 //printf("%d\n", CAT(\u306d, \u3053)); 这一行会导致它报错。使用clang至少可以得到一致的结果 - 我可以取消注释所有行并且编译器可以接受,因为使用 CAT 的结果始终与直接连接不同。 - grek40
@grek40 gcc 6.1没有抱怨,似乎是gcc 5.3.1中的一个错误(未尝试)? - user1887915
1
三字符序列,双字符序列,通用字符名称(UCN)......坚决说不。 - chqrlie
1个回答

7

您的代码并没有触发您提到的未定义行为,因为标记串联未生成通用字符名称(6.4.3)。

根据6.10.3.3,由于运算符##的左侧和右侧都是标识符,并且所生成的标记也是一个有效的预处理标记(也是标识符),因此##运算符本身不会触发未定义行为。

在阅读有关标识符(6.4.2,D.1,D.2)、通用字符名称(6.4.3)的说明后,我相信这更像是clang预处理器中的一个错误,它会将由标记串联产生的标识符和普通标识符区别对待。


没错,就是这样。5.1.1.2并没有说你不能在预处理器中操作整个UCN;它说你不能从它们的组成部分构造UCN(例如,通过\U30 ## 53的结果构造UCN是非法的)。长话短说,GCC做得对,而Clang则不然。 - Iwillnotexist Idonotexist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接