【问题标题】:Is it allowed for an enum to have an unlisted value? [duplicate]是否允许枚举具有未列出的值? [复制]
【发布时间】:2015-11-19 19:44:19
【问题描述】:

说,我们有

enum E
{
  Foo = 0,
  Bar = 1
};

现在,我们开始了

enum E v = ( enum E ) 2;

然后

switch ( v )
{
  case Foo:
    doFoo();
  break;
  case Bar:
    doBar();
  break;
  default:
    // Is the compiler required to honor this?
    doOther();
  break;
}

由于上面的开关处理枚举的每个可能列出的值,编译器是否允许优化掉上面的default 分支,或者在枚举值不在的情况下具有未指定或未定义的行为名单?

我期望 C 和 C++ 的行为应该是相似的,所以问题是关于这两种语言的。但是,如果在这种情况下 C 和 C++ 之间存在差异,那么也很高兴知道这一点。

【问题讨论】:

  • 您希望用什么语言回答这个问题?据我所知,枚举在 C 和 C++ 中的工作方式不同。
  • 我无法通过 MSVC 在 C 中编译 E v = ( E ) 2;。我收到“错误 C2065:'E':未声明的标识符”。我只能参考FooBar
  • @WeatherVane 这是因为 E 未定义为类型。要么你typedef它,要么写enum E v = 2;
  • 如果 v 在编译时已知为 2,编译器甚至可以将整个 switch 语句优化为仅调用 doOther()...
  • @ace:是的,有趣的部分是如果switch 在编译为.lib 的函数中(未启用链接时优化),然后从链接@ 的代码中使用987654335@,因此编译器不知道将使用的实际值,只是它作为参数提供。任何编译器拥有完整信息的地方都会导致奇怪的优化,从而使一般情况无效。

标签: c++ c enums language-lawyer


【解决方案1】:

C++情况

在 C++ 中,每个枚举都有一个基础的整数类型。如果明确指定(例如:enum test2 : long { a,b};)或在 作用域 枚举的情况下默认为int(例如: enum class test { a,b };):

[dcl.enum]/5: 每个枚举都定义了一个不同于所有其他类型的类型。每个枚举也有一个基础类型。 (...) 如果不 明确指定,范围枚举类型的基础类型 是整数。在这些情况下,基础类型被称为是固定的。

对于底层类型未明确固定的 unscoped 枚举(您的示例),该标准为您的编译器提供了更大的灵活性:

[dcl.enum]/7:对于底层类型不固定的枚举,底层类型是一个整数类型,可以表示所有的 枚举中定义的枚举值。 (...) 使用哪种整数类型作为底层是实现定义的 类型,但基础类型不得大于 int 除非枚举器的值不能放入 int 或 unsigned 诠释。

现在是非常棘手的事情:枚举变量可以保存的值取决于底层类型是否固定

  • 如果它是固定的,“枚举的值是 基础类型。”

  • 否则,可以容纳最小枚举数和最大枚举数的是最小位域的最小值和最大值内的整数值。

您属于第二种情况,尽管您的代码可以在大多数编译器上运行,但最小位域的大小为 1,因此您可以在所有兼容的 C++ 编译器上确定的唯一值是 0 到 1 之间的值。 ..

结论:如果你想确保值可以设置为 2,你要么必须让你的枚举成为一个作用域枚举,要么显式地指明一个底层类型。**

更多阅读:

C情况

C的情况要简单得多(C11):

6.2.5/16:枚举包含一组命名的整数常量值。每个不同的枚举构成一个不同的枚举 输入。

所以基本上,它是一个 int:

6.7.2.2./2 定义枚举常量值的表达式应为具有值的整数常量表达式 可表示为 int。

有以下限制:

每个枚举类型都应该兼容char,一个有符号整数 类型,或无符号整数类型。类型的选择是 实现定义,但应能够表示 枚举所有成员的值。

【讨论】:

  • 你对C情况的解释可能有点误导。 6.7.2.2/2 不要求它一个int,只要求值都可以表示为int。也就是说,INT_MIN <= n <= INT_MAX 代表枚举中的所有值n。 6.7.2.2/4 不是进一步的限制;它宁愿允许实现使用任何整数类型((有符号或无符号)char、int、long 或 long int (6.2.5/4-7)),只要它们能够表示所有枚举值.
  • 另外,你的回答没有提到 OP 代码的行为是什么:( enum E)2 实际上会导致 C++ 中未定义的行为,正如 defect 1766 所解决的那样。 C++14 标准有不同的措辞,但措辞没有意义,因此委员会决定它实际上是未定义的(并且缺陷报告追溯适用)。
  • @M.M 感谢您强调了模棱两可的措辞。我已经改写了这句话。
  • @Ray 你当然是对的:最后一个引号的措辞让编译器可以选择较小的整数类型,例如 char,只要它可以保存所有枚举值(我意思是“限制”)。它也可以允许较大的类型,例如 long,但根据 6.7.2.2。常量表达式必须可以表示为 int
【解决方案2】:

在 C 中,enum 类型是一个足够大的整数类型,可以容纳所有 enum 常量:

(C11, 6.7.2.2p4) “每个枚举类型应与 char、有符号整数类型或无符号整数类型兼容。类型的选择是实现定义的,110) 但应能够表示枚举所有成员的值”。

假设enum E 的选定类型是_Bool_Bool 对象只能存储值01。在不调用未定义行为的情况下,不可能让 _Bool 对象存储不同于 01 的值。

在这种情况下,允许编译器假定enum E 类型的对象只能在严格符合的程序中保存01,因此允许优化default 开关情况。

【讨论】:

  • @Deduplicator C11 指定了 6.2.7 中类型兼容性的含义,但基本上类型兼容性意味着类型可以互换使用(例如,signed intint
  • 缺少链接:_Bool 在 C 中是“无符号整数类型”。(bool 在 C++ 中不是。)
  • 保证背后的想法(在 C 中和非固定 C++ 中)是枚举数与 | 的任何组合都会产生有效值
【解决方案3】:

C++Std 7.2.7 [dcl.enum]:

可以定义一个枚举,其值未由其任何枚举​​器定义。

因此,您可以拥有未在枚举器列表中列出的枚举值。

但在您的具体情况下,“基础类型”不是“固定的”(7.2.5)。规范没有说明在这种情况下哪个是底层类型,但它必须是整数。由于 char 是此类最小的类型,因此我们可以得出结论,枚举数列表中还有其他未指定的值。

顺便说一句,我认为编译器可以在确定没有其他值分配给 v 时优化你的情况,这是安全的,但我认为还没有那么聪明的编译器。

【讨论】:

  • 我以为这是关于标准的问题,我只是快速浏览了一下,发现没有其他人在引用它。如果没有 C++ 标准,您将无法真正回答这个问题。
  • @Deduplicator cppreference on enums 说:整数 (..) 的值可以通过 static_cast 等方式转换为任何枚举类型。如果转换为枚举的基础类型的值超出此枚举的范围,则结果为未指定 (C++17 前)未定义行为 (C++17 起)。如果基础类型是固定的,则范围是基础类型的范围。如果底层类型不固定,则范围是最小位字段的所有可能值,该位字段大到足以容纳目标枚举的所有枚举数。.
  • 这对吗,关于位域的部分是正式的范围?
  • @MicroVirus:不确定是未指定还是未定义,但整个段落都支持这一点。
【解决方案4】:

另外,7.2/10:

算术或枚举类型的表达式可以转换为 显式枚举类型。如果它在 枚举类型的枚举值范围;否则 结果枚举值未指定。

【讨论】:

  • 那么问题来了:枚举类型的范围是多少?
  • 7.2/2:带有 = 的枚举器定义为关联的枚举器提供了由常量表达式指示的值。常量表达式应该是一个整数常量表达式(5.19)。如果第一个枚举器没有初始化器,则相应常量的值为零。没有初始化器的枚举器定义为枚举器提供了通过将前一个枚举器的值增加一而获得的值。所以在enum E { Foo = 0, Bar = 1 };的情况下,2不在枚举值范围内。
【解决方案5】:

在 C 中,枚举器的类型为 int 。因此,任何整数值都可以分配给枚举类型的对象。

来自 C 标准(6.7.2.2 枚举说明符)

3 枚举器列表中的标识符被声明为常量 具有 int 类型,并且可以出现在任何允许的地方。

在 C++ 中,枚举器具有定义它的枚举类型。在 C++ 中,您应该明确指定底层类型,或者编译器自行计算允许的最大值。

来自 C++ 标准(7.2 枚举声明)

5 每个枚举都定义了一个不同于所有其他类型的类型。每个枚举也有一个基础类型。可以使用 enum-base 显式指定底层类型;如果未明确指定,则作用域枚举类型的基础类型是 int。在这些情况下,基础类型被称为是固定的。在枚举说明符的右大括号之后,每个枚举数都有其枚举的类型

因此,在 C 中,枚举的任何可能值都是任何整数值。编译器可能不会优化移除默认标签的开关。

【讨论】:

  • 标识符是某种形式的 int,因为它们是常量,但枚举类型本身不是 int。我认为这是症结所在。
【解决方案6】:

在 C 和 C++ 中,这可以工作。

两者的代码相同:

#include <stdio.h>

enum E
{
  Foo = 0,
  Bar = 1
};

int main()
{
    enum E v = (enum E)2;    // the cast is required for C++, but not for C
    printf("v = %d\n", v);
    switch (v) {
    case Foo:
        printf("got foo\n");
        break;
    case Bar:
        printf("got bar\n");
        break;
    default:
        printf("got \n", v);
        break;
    }
}

两者的输出相同:

v = 2
got default

在 C 中,enum 是整数类型,因此您可以为其分配整数值而无需强制转换。在 C++ 中,enum 是它自己的类型。

【讨论】:

  • 这是语言保证吗(C 或 C++)?如果switchenum 处理所有合法的cases,编译器放弃default 的情况似乎是合理的。任何给定的编译器都没有(或没有特定标志)这一事实对可移植性没有用处;我怀疑这是未定义的行为,因此编译器可以为所欲为。使用 C++11 强类型 enums 是否会通过提供更好的保证来影响这一点(假设 C 和 C++ 的弱类型 enum 允许保存未声明的值)?
  • 正如其他一些答案指出的那样,但不是很清楚,编译器可以通过使用表示枚举值所需的最小字节宽度来优化内存(或多或少)。 10 年前,我在使用 GCC 交叉编译 PalmOS 代码时遇到了这个问题。例如,0-255 范围内的枚举值只需要一个字节的存储空间。将值 256 分配给该枚举类型的变量显然是行不通的;我从来没有尝试过,所以我不知道结果会是什么。 (我的记忆很模糊,但我想我必须禁用 PalmOS 代码的可变宽度枚举。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-24
  • 2023-04-02
相关资源
最近更新 更多