"control reaches end of non-void function" 在枚举类型上完全处理大小写切换

"control reaches end of non-void function" with fully handled case switch over an enum type

为什么即使处理了 type_t 的所有可能值,此代码也会触发 "control reaches end of non-void function"?处理此警告的最佳方法是什么?在switch后面加一个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

一般来说,enum是不排他的。例如,有人可以像 useType( (type_t)3 ); 这样调用您的函数。 C++14 [dcl.enum]/8:

中专门提到了这一点

It is possible to define an enumeration that has values not defined by any of its enumerators.

现在,关于哪些其他类型的枚举可以使用哪些其他值,有很多规则。

有两类枚举。第一个是固定基础类型,例如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 文本有缺陷并已将其替换为以下内容:

A value of integral or enumeration type can be explicitly converted to a complete enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the behavior is undefined.

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


要删除警告,请添加一个 default: 案例来执行某些操作。为了防御性编程的利益,我还建议,无论如何都有一个默认情况是个好主意。在实践中,它可能会导致 'contain' 未定义的行为:如果有人确实传递了一个无效值,那么您可以彻底抛出或中止。


注意:关于警告本身:编译器不可能准确地当且仅当控制流到达终点时发出警告的功能,因为这将需要解决停机问题。

他们往往过于谨慎:编译器如果不完全确定就会发出警告,这意味着存在误报。

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

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

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

的答案中指出的那样,无论如何这是未定义的行为

这样,您就可以在不引入新警告的情况下摆脱 GCC 和 Clang 当前版本的警告。 如果您错过了对 switch 语句执行 运行 的有效情况,您将失去编译器的保护。如果 GCC 和 Clang 完全错过一个 switch case(例如,如果一个值被添加到枚举中),GCC 和 Clang 都会在某种程度上减轻这种情况,但如果其中一个 运行s 变成 break声明。