“控制到达非空函数的结束”,在枚举类型上使用完全处理的情况切换

时间:2015-11-09 10:37:55

标签: c++ g++ compiler-warnings

为什么即使处理了type_t的所有可能值,此代码也会触发“控制到达非void函数的结尾”?处理此警告的最佳方法是什么?在切换后添加return -1?(代码测试here

typedef enum {
    A,
    B
} type_t;

int useType(type_t x) {
    switch (x) {
        case A:
            return 0;
        case B:
            return 1;
    }
}

<小时/> 相关:Detecting if casting an int to an enum results into a non-enumerated value

3 个答案:

答案 0 :(得分:11)

通常,enum不是唯一的。有人可以像useType( (type_t)3 );那样调用你的函数。这在C ++ 14 [dcl.enum] / 8中具体提到:

  

可以定义一个枚举,该枚举的值不是由任何枚举器定义的。

现在,有一系列规则确切地指出哪些其他值可以用于其他类型的枚举。

有两类枚举。第一个是固定底层类型,例如enum type_t : intenum class type_t。在这些情况下,基础类型的所有值都是有效的枚举数。

第二个是未修复的基础类型,其中包括诸如你的C ++ 11之前的枚举。在这种情况下,可以通过以下方式总结关于值的规则:计算为了存储枚举的所有值所需的最小位数;那么在这个位数中可以表示的任何数字都是有效值。

所以 - 在您的具体情况下,单个位可以包含值AB,因此3不是枚举器的有效值。

但如果你的枚举是A,B,C,那么即使没有具体列出3,它也是上述规则的有效值。 (所以我们可以看到几乎所有的枚举都不是排他性的)。

现在,我们需要查看有关实际尝试将3转换为type_t时会发生什么情况的规则。转换规则是C ++ 14 [expr.static.cast] / 10,它表示会生成一个未指定的值。

但是,CWG issue 1766认识到C ++ 14文本存在缺陷,并将其替换为以下内容:

  

可以将整数或枚举类型的值显式转换为完整的枚举类型。如果原始值在枚举值(7.2)的范围内,则该值不变。 否则,行为未定义。

因此,在具有值01的两个枚举器的特定情况下,除非程序已触发未定义的行为,否则不可能有其他值,因此警告可被视为误报

要删除警告,请添加default:案例。为了防御性编程,我还建议无论如何都要有默认案例。在实践中,它可以用于包含&#39;未定义的行为:如果有人碰巧传递了无效值,那么你可以干净地抛出或中止。

NB:关于警告本身:当且仅当控制流到达函数末尾时,编译器才能准确警告,因为这需要解决停止问题。

他们倾向于谨慎行事:编译器会发出警告,如果不完全确定,则表示存在误报。

因此,此警告的存在并不一定表明可执行文件实际上允许进入默认路径。

答案 1 :(得分:1)

回答第二个问题(“处理此警告的最佳方法是什么?”)

在我看来,通常,最好的方法是在switch语句之后添加对__builtin_unreachable()的调用(在GCC和Clang中都可用-也许有时会得到[[unreachable]])。 这样,您可以明确地告诉编译器代码永远不会在switch语句上运行。如果确实如此,您很乐意接受不确定行为的所有可怕后果。请注意,最明显的原因是一个枚举包含一个未列出的值-无论如何,这是未定义的行为,正如@ M.M的答案中指出的那样。

这样,您无需使用新的警告,就可以摆脱当前版本的GCC和Clang的警告。 如果您错过了在switch语句上运行的有效情况,则丢失的是编译器提供的保护。这在某种程度上减轻了GCC和Clang都会在完全遗漏切换用例时发出警告(例如,如果将值添加到枚举中),但是在其中一种情况遇到break语句时则不会警告您。 / p>

答案 2 :(得分:0)

如果您完全确定x不会是AB以外的其他东西(例如,如果它是班级的私有成员),则可以使用{ {3}}:

int useType(type_t x) {
    switch (x) {
        case A:
            return 0;
        case B:
            return 1;
    }
    assert(false);
}