永远不会达到打开枚举后的保护代码
Guard code after switch on enum is never reached
我在尝试使用 g++ 4.4.3 编译一些代码时遇到了一个令人困惑的问题。
下面的代码编译得很好,但是当我传递一个"无效"的枚举值时,该函数没有达到预期的断言,而是只返回 1。我发现更奇怪的是,当我取消注释与 E3 枚举值相关的行时,事情开始按预期工作。
交换机块中没有默认条目的事实是设计使然。我们使用 -Wall 选项进行编译,以获取未处理的枚举值的警告。
enum MyEnum
{
E1,
E2,
//E3
};
int doSomethingWithEnum(MyEnum myEnum)
{
switch (myEnum)
{
case E1: return 1;
case E2: return 2;
//case E3: return 3;
}
assert(!"Should never get here");
return -1;
}
int main(int argc, char **argv)
{
// Should trigger assert, but actually returns 1
int retVal = doSomethingWithEnum(static_cast<MyEnum>(4));
std::cout << "RetVal=" << retVal << std::endl;
return 0;
}
你的switch语句将被编译成这个(g++ 4.4.5(:
cmpl $1, %eax
je .L3
movl $1, %eax
jmp .L4
.L3:
movl $2, %eax
.L4:
leave
ret
可以看出,断言被完全优化,编译器选择与 E2 进行比较并在所有其他情况下返回 1。 使用三个枚举值,它无法做到这一点。
C++98标准(静态铸造(的第5.2.9节给出了允许这样做的原因:
整型或枚举类型的值可以显式转换为枚举类型。值不变 如果原始值在枚举值 (7.2( 的范围内。否则,生成的枚举值为 未指定。
换句话说,如果您尝试使用非法值,编译器可以自由使用它想要的任何枚举值(在本例中为 E1(。 这包括做直观的事情并使用提供的非法值或根据情况使用不同的值,这就是为什么行为会根据枚举值的数量而变化的原因。
它看起来不像编译器错误。它更像是一种未定义的行为。该规则规定,如果枚举范围内的未定义值,则可以将其归因于枚举。在您的情况下,编译器只需要一个位来表示枚举,因此它可能正在执行某种优化。
这是完全正常的,您的代码依赖于未指定的行为。
在C++中,枚举只应该保存其较低值和较高值之间的值。编译器可以转换或忽略任何其他内容。
例如,如果我有enum Foo { min = 10, max = 11 };
那么编译器可以自由地用一个有效位表示它,并在我要求打印它或将其转换为整数时添加 10。
一般来说,由于性能成本,编译器不会利用这种"压缩",他们只是选择一个可以容纳所有值和0
的基础类型。大多数情况下,除非int
太小,否则它是int
。
但是,这并不妨碍他们假定此int
包含的值仅限于您定义的范围。在您的情况下:[0, 2)
.
因此,switch
语句可以简单地优化为与0
进行比较,因为您指定了枚举只能0
或1
。
同样,if (foo == 3)
可以被认为是同义重复的比较(总是错误的(并进行了优化。
事实上,正如汉斯·帕萨特(Hans Passat(发出的gcc"错误"中所描述的那样,这种优化通常发生在gcc的2s边界的幂上。因此,例如,如果您enum { zero, one, two, three };
并为其分配了4
或更高,则会发生相同的行为。
请注意,实际存储的值不受影响!这就是为什么 print 语句按预期工作的原因,并且是混淆的根源。
我不知道是否有警告表明在此枚举中存储 4 将导致未指定的行为。无论如何,它仅适用于编译时值...
<小时 />汉斯·帕萨特(Hans Passat(提供了gcc"错误",可以跟踪此"问题"。
Emil Styrke提供了理由(这是作用域枚举的更新引用(:
5.2.9/10
整型或枚举类型的值可以显式转换为枚举类型。如果原始值在枚举值 (7.2( 的范围内,则该值保持不变。否则,结果值未指定(并且可能不在该范围内(。浮点类型的值也可以转换为枚举类型。结果值与将原始值转换为枚举的基础类型 (4.9( 以及随后转换为枚举类型相同。
- 不带大括号的枚举形式
- 枚举环境变量的惯用C++14/C++17方法
- 类似枚举的计算常量
- 如何正确实现和访问运算符的各种自定义枚举器
- 错误:从"int"到枚举c++的转换无效
- C++中构造函数中的枚举
- 访问在 C++ 结构中声明的枚举变量
- 枚举类'classname'的多重定义
- 强枚举类型定义:Clang Bug 还是 C++11 标准不确定性?
- typedef 枚举和枚举类有什么区别?
- 为什么我的开关/机箱在使用枚举时默认?
- 标准::可选枚举的比较运算符
- C++两个源文件之间共享的枚举的静态实例
- 打印没有铸件的枚举可以在C++中吗?
- 枚举成员与静态 int 成员?
- 是否可以基于私有/受保护成员分配类枚举?
- 受保护的枚举不被视为类型,为什么?
- 永远不会达到打开枚举后的保护代码
- 类中受保护的静态枚举的链接器错误,即使已经定义
- 以前声明为受保护的枚举的访问枚举器