我尝试使用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;
}
答案 0 :(得分:9)
你的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)。否则,生成的枚举值为 unspeci音响编
换句话说,如果您尝试使用非法值,编译器可以自由使用它想要的任何枚举值(在本例中为E1)。这包括做直观的事情并使用提供的非法值或根据具体情况使用不同的值,这就是行为根据枚举值的数量而变化的原因。
答案 1 :(得分:8)
它看起来不像编译器错误。它更像是一种未定义的行为。 该规则表明您可以将未定义的值归因于Enum IF此值在枚举范围内。 在您的情况下,编译器只需要一位来表示枚举,因此它可能正在进行某种优化。
答案 2 :(得分:7)
这是完全正常的,您的代码依赖于未指定的行为。
在C ++中,枚举只能在其较低和较高值之间保存值。编译器可以转换或忽略任何其他内容。
例如,如果我有enum Foo { min = 10, max = 11 };
,则编译器可以用一个有效位表示它,并在我要求打印它或将其转换为整数时添加10。
一般情况下,由于性能成本的原因,编译器没有利用这种“压缩”,他们只需选择一个可以容纳所有值的基础类型0
。最常见的是int
,除非int
太小。
但是,它并不妨碍他们假设此int
包含的值仅限于您定义的范围。在您的情况下:[0, 2)
。
因此,switch
语句可以简单地优化为与0
的比较,因为您指定的枚举只能是0
或1
。
同样,if (foo == 3)
可以被认为是同义反复(总是假的)并且被优化掉了。
事实上,正如Hans Passat发出的gcc“bug”中所描述的,这种优化通常发生在gcc的2s边界上。例如,如果您有enum { zero, one, two, three };
并为其分配4
或更高版本,则会发生相同的行为。
请注意,实际存储的值不受影响!这就是print语句按预期工作的原因,也是混淆的原因。
我不知道是否有警告表明在此枚举中存储4将导致未指定的行为。在任何情况下,它只适用于编译时值......
Hans Passat提供了gcc "bug",可以跟踪这个“问题”。
Emil Styrke提供了理由(这里是范围内容的更新引用):
<强> 5.2.9 / 10 强>
可以将整数或枚举类型的值显式转换为枚举类型。如果原始值在枚举值(7.2)的范围内,则该值不变。否则,结果值未指定(可能不在该范围内)。浮点类型的值也可以转换为枚举类型。结果值与将原始值转换为枚举的基础类型(4.9)以及随后的枚举类型相同。