"control reaches end of non-void function",通过枚举类型进行完全处理的案例切换

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

本文关键字:处理 案例 类型 枚举 end reaches control of non-void function      更新时间:2023-10-16

为什么即使处理了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;
    }
}


相关:检测将int强制转换为枚举是否会导致非枚举值

一般来说,enum s不是独占的。例如,有人可以像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,它表示生成一个未指定的值。

然而,《化学武器公约》第1766期承认C++14文本有缺陷,并将其替换为以下内容:

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

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


要删除警告,请添加一个有作用的default:事例。我还建议,为了防御编程的利益,无论如何,有一个默认情况是个好主意。在实践中,它可能会"包含"未定义的行为:如果有人碰巧传递了一个无效值,那么你可以干净地抛出或中止。


注意:关于警告本身:编译器不可能在控制流到达函数末尾时准确地发出警告,因为这需要解决停止问题。

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

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

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

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

通过这种方式,您可以消除GCC和Clang当前版本上的警告,而不引入新的警告。如果您错过了在switch语句上运行的有效情况,那么您将失去编译器的保护。这在一定程度上得到了缓解,GCC和Clang都会警告您,如果完全错过了开关情况(例如,如果将值添加到枚举中),但如果其中一个情况遇到break语句,则不会发出警告。