为什么即使处理了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
答案 0 :(得分:11)
通常,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 issue 1766认识到C ++ 14文本存在缺陷,并将其替换为以下内容:
可以将整数或枚举类型的值显式转换为完整的枚举类型。如果原始值在枚举值(7.2)的范围内,则该值不变。 否则,行为未定义。
因此,在具有值0
和1
的两个枚举器的特定情况下,除非程序已触发未定义的行为,否则不可能有其他值,因此警告可被视为误报
要删除警告,请添加default:
案例。为了防御性编程,我还建议无论如何都要有默认案例。在实践中,它可以用于包含&#39;未定义的行为:如果有人碰巧传递了无效值,那么你可以干净地抛出或中止。
NB:关于警告本身:当且仅当控制流到达函数末尾时,编译器才能准确警告,因为这需要解决停止问题。
他们倾向于谨慎行事:编译器会发出警告,如果不完全确定,则表示存在误报。
因此,此警告的存在并不一定表明可执行文件实际上允许进入默认路径。
答案 1 :(得分:1)
回答第二个问题(“处理此警告的最佳方法是什么?”)
在我看来,通常,最好的方法是在switch语句之后添加对__builtin_unreachable()
的调用(在GCC和Clang中都可用-也许有时会得到[[unreachable]]
)。
这样,您可以明确地告诉编译器代码永远不会在switch语句上运行。如果确实如此,您很乐意接受不确定行为的所有可怕后果。请注意,最明显的原因是一个枚举包含一个未列出的值-无论如何,这是未定义的行为,正如@ M.M的答案中指出的那样。
这样,您无需使用新的警告,就可以摆脱当前版本的GCC和Clang的警告。
如果您错过了在switch语句上运行的有效情况,则丢失的是编译器提供的保护。这在某种程度上减轻了GCC和Clang都会在完全遗漏切换用例时发出警告(例如,如果将值添加到枚举中),但是在其中一种情况遇到break
语句时则不会警告您。 / p>
答案 2 :(得分:0)
如果您完全确定x
不会是A
或B
以外的其他东西(例如,如果它是班级的私有成员),则可以使用{ {3}}:
int useType(type_t x) {
switch (x) {
case A:
return 0;
case B:
return 1;
}
assert(false);
}