【问题标题】:C11 and constant expression evaluation in switch-case labelsC11 和 switch-case 标签中的常量表达式求值
【发布时间】:2018-11-03 18:29:49
【问题描述】:

下面这个问题Why doesn't gcc allow a const int as a case expression?,和What promoted types are used for switch-case expression comparison?或者Is there any way to use a constant array with constant index as switch case label in C?基本一样。

从第一个链接,我尝试替换:

case FOO:                       // aka 'const int FOO = 10'

与:

case ((int) "toto"[0]):         // can't be anything *but* constant

这给出了:

https://ideone.com/n1bmIb -> https://ideone.com/4aOSXR = 在 C++ 中工作

https://ideone.com/n1bmIb -> https://ideone.com/RrnO2R = 在 C 中失败

我不太明白,因为“toto”字符串不能是任何东西是一个常量,它甚至不是一个变量,它位于编译器内存的空白处。我什至没有玩过 C 语言的 'const' 模糊逻辑(它真的代表“只读,而不是常量,你期望什么?”),问题是“数组访问”或“指针引用” " 转换成在 C 中不求值但在 C++ 中表现良好的常量表达式。

我希望使用这个“技巧”来使用 HASH_MACRO(str) 从键标识符生成唯一的案例标签值,最终让编译器在发生冲突时引发错误,因为发现了类似的标签值。

好的,好的,有人告诉我这些限制是为了简化语言工具(preproc、编译器、链接器),而且 C 不是没有 LISP,但您可以拥有功能齐全的 LISP 解释器/编译器,其大小仅为一个 C 等价物,所以这不是借口。

问题是:C11 是否有一个“扩展”,只允许这个“toto”东西在 GCC、CLANG 和... MSVC 中工作?我不想走 C++ 路径(typedef 的前向声明不再起作用)并且因为嵌入的东西(因此编译时散列计算会导致时空失真)。

是否有一种中间“C+”语言更“宽容”和“理解”嵌入得更好一些,比如-Praise the Lords-“枚举作为位域成员”,以及我们无法拥有的其他好东西(因为 out现实标准的演变就像沙漠阳光下的蜗牛)?

#provemewrong、#changemymind、#norustplease

【问题讨论】:

  • 我不知道为什么这被否决了——尤其是没有任何 cmets。这实际上是一个有趣的问题,需要对 C 标准进行一些思考和了解才能回答。不知道为什么它不起作用的人不会知道到哪里去寻找答案。
  • TL;DR 的答案是:因为 C 中整数常量表达式的定义是有缺陷的。他们在 C++ 中修复了它。
  • @AndrewHenle 我注意到一个几乎没有声誉的用户会很快被否决,而声誉较高的用户 [~>2k] - 即使是我问自己的问题 - 快速获得投票。我对这个问题投了赞成票,因为与一些初学者问题相比,我认为它很好,而且还有别的东西。
  • @Al Bundy :谢谢,我不经常使用 stackoverflow,因为我发现它...... 溢出 基本问题。另一方面,我在 Codeproject 上有很好的记录,所以我经常在那里找到我的运气。但由于他们更多是 C++ 或 C# 专家,而且我的问题专门针对 C,所以我来到这里。而且我不在乎愤怒投票,这不会影响我的生活,只要像你这样的好人更加精心和建设性。
  • 总而言之,Stack Overflow 问题和 cmets 不适合发表宣言。我建议你在别处写一篇关于这个的文章。

标签: c++ c switch-statement constants constant-expression


【解决方案1】:

编译器在编译时是否知道它并不重要。 case 标签的值必须是 整数常量表达式 (C11 6.8.4.2p3)

  1. 每个case标签的表达式应该是一个整数常量表达式,并且同一switch语句中的两个case常量表达式在转换后不能有相同的值。 switch 语句中最多可以有一个默认标签。 (任何封闭的 switch 语句都可能有一个默认标签或 case 常量表达式,其值与封闭 switch 语句中的 case 常量表达式重复。)

而整数常量表达式的定义是in C11 6.6p6

  1. 整数常量表达式应具有整数类型,并且只能具有以下操作数:整数常量、枚举常量、字符常量、sizeof 其结果为整数常量的表达式、_Alignof 表达式和作为直接操作数的浮点常量的演员表。整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,除非作为 sizeof_Alignof 运算符的操作数的一部分。

由于"toto" 不是整数常量、枚举常量、字符常量、常量sizeof_Alignof 表达式或浮点常量转换为整数;并且该列表在标准的 constraints 部分中指定,编译器不得静默传递。 (即使符合标准的编译器仍然可以成功编译程序,但它必须将此诊断为违反约束。)


您可以使用链式? : 将索引解析为字符常量,即

  x == 0 ? 't' 
: x == 1 ? 'o'
: x == 2 ? 't'
: x == 3 ? 'o'

这可以写成宏。

【讨论】:

  • this 不能声明为宏,因为 HASH_MACRO(str) 会生成如下内容: case ( ( ( (('\0' == "toto"[0]) ? ((cnU32 ) 0) : "toto"[0] + ((('\0' == "toto"[0 +1]) ? (65599ULL) : "toto"[0 +1] + ((('\0' == "toto"[0 +1 +1]) ? (65599ULL) : "toto"[0 +1 +1] + ((('\0' == "toto"[0 +1 +1 +1] ) ? (65599ULL) : "toto"[0 +1 +1 +1] + ((('\0' == "toto"[0 +1 +1 +1 +1]) ? (65599ULL) : "toto "[0 +1 +1 +1 +1] + ((('\0' == "toto"[0 +1 +1 +1 +1 +1]) ? (65599ULL) : "toto"[0 + 1 +1 +1 +1 +1] + ((('\0' == "toto"[0 +1 +1 +1 +1 +1 +1]) ? (65599ULL) : "toto"[0 + 1 +1 +1 +1 +1 +1] + ...
  • 您需要将单个字符作为参数。
  • "您需要将单个字符作为参数。" -> 不,只是……不。我们在 2018 年,在过去的 40 年里,有些事情应该有所改进,预处理器和编译器中的字符串处理应该是其中之一。甚至 Python3 在这方面也有所改进。
  • 您可以与 C 标准委员会讨论。
  • @Christian Gibbons :只有在获得诺贝尔奖的情况下。不,说真的,我的意思是这个 C++ 特性被移植到 C 中是没有运气的。那太明显了。如果我尝试过,那不会在 C39 之前。
【解决方案2】:

"toto[0]" 不是一个 整数常量表达式,因为 C 定义了这个术语:

6.6 常量表达式
...
6    整数常量表达式117) 应该是整数类型并且应该只有操作数 即整数常量、枚举常量、字符常量、sizeof 结果是整数常量、_Alignof 表达式和浮点数的表达式 作为强制转换的直接操作数的常量。在整数常量中转换运算符 表达式只能将算术类型转换为整数类型,除非作为 sizeof_Alignof 运算符的操作数。
117) 在许多上下文中都需要整数常量表达式,例如位域的大小 结构的成员,枚举常量的值,以及非可变长度的大小 大批。适用于条件包含中使用的整数常量表达式的进一步约束 预处理指令在 6.10.1 中讨论。

C 2011 online draft

【讨论】:

    【解决方案3】:

    您遇到的问题是,在 C 中,“toto”是一个字符数组。当然,它在内存中是不变的,但它仍然只是一个数组。 [] 运算符在数组中索引(来自指针)。如果需要,您可以编辑已编译的二进制文件并将字符串“toto”更改为其他内容。从某种意义上说,它不是编译时已知的。相当于做:

    char * const ___string1 = "toto";
    ...
    case ((int) ___string1[0]):
    

    (这有点强迫和多余,但只是为了演示)

    请注意,字符串文字的元素类型是char,而不是const char

    大小写必须是常量,因为它内置在编译的程序控制流中。

    【讨论】:

    • 问题是我特别不希望“toto”字符串出现在我的二进制文件中。我只是不需要它,我只是需要一个唯一标识符(这里是一个案例标签)而不必维护一个大的枚举,另一方面 与 C 兼容,被视为整数常量。
    • @Kochise 这不是 C 的工作方式。您告诉它您希望该 char 数组存在。每当您使用字符串文字时,您实际上是在使用指针。枚举及其命名值分别本质上是整数类型的 typedef 和整数常量的 #defines。它们一点也不特别。
    • 然后告诉我我是否错了,但是字符串文字是 C 标准的一部分,预处理器甚至可以整理它们(“Hello”“World!”)所以它 是 i> 可以扫描这些。同样,数组访问也是语言的一部分,我不明白为什么预处理器甚至在编译时都不能处理常量表达式。我的意思是,并不是一开始就没有专门的功能。并且 C 以这种方式制作的,不应该阻止它发展和简化开发过程,而是留在计算机语言的黑暗时代。
    • 字符串排序是一个预处理函数。这很好,因为字符串是指针,所以将两个字符串相邻放置不是您可以编译的东西。预处理器不在乎你是否在 switch-case 中,这不是它的工作。
    • 是的,很好,然后告诉我为什么你不能明确地在预处理器中做指针事情(即。'((char) "toto" + 2)' 或'"toto"[2]') 但预处理器可以像你提到的排序规则那样隐式地做到这一点?类似的东西在 C++11 中可用,但在 C11 中不可用,而不是针对 C++ 特定的东西(类、模板等),并且应该被向后移植到 C11 中,以至少在两种语言之间产生一种一致性的错觉。我知道,我在抱怨,但是这样的规范化委员会定期开会,无法解决现实生活中的问题……
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-12
    • 1970-01-01
    • 2011-03-28
    • 2013-05-15
    相关资源
    最近更新 更多