【问题标题】:Creating identifiers containing universal character names via token concatenation通过令牌连接创建包含通用字符名称的标识符
【发布时间】:2016-08-15 17:35:09
【问题描述】:

我编写了这段代码,它通过标记连接创建包含通用字符名称的标识符。

//#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 with -fextended-identifiers optiongcc 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。喜欢做

赋值(\u0001,0401,a,b,4)

只连接碰巧在任何地方包含 UCN 的东西是可以的。

还有一条日志显示a code example like this case (but with 4 characters)another code example替换。

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

【问题讨论】:

  • 就我而言,gcc 5.3.1 看起来更糟糕...取消注释 //printf("%d\n", CAT(\u306d, \u3053)); 行并抱怨。使用 clang 我至少可以得到一致的结果 - 我可以取消注释所有行并接受它,因为使用 CAT 的结果始终不同于直接连接。
  • @grek40 gcc 6.1 并没有抱怨它,似乎是在 gcc 5.3.1 中(未尝试)?
  • 三合字母、二合字母、UCN...只要说不。

标签: c clang language-lawyer


【解决方案1】:

您的代码没有触发您提到的未定义行为,因为通用字符名称 (6.4.3) 不是由令牌连接产生的。

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

在阅读了关于标识符(6.4.2、D.1、D.2)、通用字符名称(6.4.3)的描述后,我很确定它更像是 clang 预处理器中的一个错误,它处理标识符由令牌连接和普通标识符产生的不同。

【讨论】:

  • +1,正是这个。 5.1.1.2 并没有说你不能在预处理器中操作整个 UCN;它说您不能从它们的组成部分制作 UCN(例如,由于\U30 ## 53 而制作 UCN 是非法的)。长话短说,GCC 的做法是正确的,而 Clang 则不然。
猜你喜欢
  • 1970-01-01
  • 2020-01-23
  • 2015-02-20
  • 1970-01-01
  • 2014-02-24
  • 1970-01-01
  • 2020-05-08
  • 1970-01-01
  • 2014-06-04
相关资源
最近更新 更多