永远不会达到打开枚举后的保护代码

  • 本文关键字:枚举 保护 代码 永远 c++
  • 更新时间 :
  • 英文 :


我在尝试使用 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进行比较,因为您指定了枚举只能01

同样,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( 以及随后转换为枚举类型相同。

最新更新