为什么即使处理了
type_t
的所有可能值,此代码也会触发“控制到达非 void 函数的末尾”?处理此警告的最佳方法是什么?在开关后面添加 return -1
?typedef enum {
A,
B
} type_t;
int useType(type_t x) {
switch (x) {
case A:
return 0;
case B:
return 1;
}
}
一般来说,
enum
并不具有排他性。例如,有人可以像 useType( (type_t)3 );
这样调用你的函数。 C++14 [dcl.enum]/8 中特别提到了这一点:
可以定义一个具有其任何枚举器未定义的值的枚举。
现在,有一堆规则关于哪些其他值对于其他类型的枚举来说是可能的。
枚举有两类。第一个是固定基础类型,例如
enum type_t : int
,或enum class type_t
。在这些情况下,基础类型的所有值都是有效的枚举器。
第二个是不固定的基础类型,其中包括 C++11 之前的枚举,例如您的枚举。在这种情况下,关于值的规则可以总结为:计算存储枚举的所有值所需的最小位数;那么任何可用该位数表示的数字都是有效值。
因此 - 在您的具体情况下,单个位可以同时保存值
A
和 B
,因此 3
对于枚举器来说不是有效值。
但是如果您的枚举是
A,B,C
,那么即使 3
没有具体列出,根据上述规则它也是一个有效值。 (所以我们可以看到几乎所有的枚举都不会具有排他性)。
现在我们需要看看如果有人确实尝试将
3
转换为 type_t
会发生什么情况的规则。转换规则是C++14 [expr.static.cast]/10,它表示生成一个未指定的值。
但是,CWG 第 1766 期 认识到 C++14 文本有缺陷,并将其替换为以下内容:
整型或枚举类型的值可以显式转换为完全枚举类型。如果原始值在枚举值(7.2)的范围内,则该值不变。 否则,行为未定义。
因此,在您的特定情况下,恰好有两个具有值
0
和 1
的枚举器,除非程序已经触发了未定义的行为,否则不可能有其他值,因此警告可能被视为误报。
要删除警告,请添加一个执行某些操作的
default:
案例。我还建议,为了防御性编程的利益,无论如何,有一个默认情况是个好主意。在实践中,它可能有助于“包含”未定义的行为:如果有人碰巧传递了无效值,那么您可以干净地抛出或中止。
注意:关于警告本身:当且仅当控制流到达函数末尾时,编译器不可能“准确”地发出警告,因为这需要解决暂停问题。 他们倾向于谨慎行事:如果不完全确定,编译器会发出警告,这意味着存在误报。
因此,此警告的存在并不一定表明可执行文件实际上允许进入默认路径。
在我看来,通常最好的方法是在 switch 语句之后添加对
__builtin_unreachable()
的调用(在 GCC 和 Clang 中都可用 - 也许在某些时候我们会得到
[[unreachable]]
)。 这样,您就可以明确告诉编译器代码永远不会在 switch 语句上运行。如果确实如此,您很乐意接受未定义行为的所有可怕后果。请注意,运行的最明显原因是包含未列出的值的枚举 - 正如 @M.M. 的答案中指出的那样,无论如何,这都是未定义的行为。这样,您就可以消除当前版本的 GCC 和 Clang 上的警告,而无需引入新的警告。 如果您错过了确实在 switch 语句上运行的有效情况,那么您将失去编译器的保护。如果完全错过 switch case(例如,如果将值添加到枚举中),GCC 和 Clang 都会警告您,但如果其中一种情况遇到
break
语句,则不会在某种程度上缓解这种情况。我对 C 和 gcc 11.4 也有同样的问题,我发现切换后的