使用switch语句无法返回功能

时间:2018-08-30 19:28:35

标签: c++ function switch-statement return

我正在使用旧版gcc(如果我没记错的话,是7.)开发LINUX中的应用程序。 最近,我尝试在Windows上运行相同的应用程序。 在Windows上,我将MinGW用作编译器(带有gcc 8.1.0)。

在Windows上编译我的应用程序时遇到了此错误消息:

  

警告:控制到达非空函数[-Wreturn-type]

的末尾

代码类似于以下内容:

class myClass {
protected:
    enum class myEnum{
        a,
        b,
    };

    int fun(myClass::myEnum e);
}

int myClass::fun(myClass::myEnum e) {
    switch (e){
        case myEnum::a:{
            return 0;
        }
        case myEnum::b:{
            return 1;
        }
    }
}

我理解错误消息的含义,我只是想知道为什么它在LINUX中从来不是问题。

这段代码真的是一个问题,我是否必须添加一些虚拟的return语句?

此函数的分支是否会导致没有return语句?

4 个答案:

答案 0 :(得分:4)

这是g ++静态分析器的缺点。没有得到所有枚举值都在switch语句中正确处理的事实。

您可以在此处https://godbolt.org/z/LQnBNi中注意到,clang不会对其当前形状的代码发出任何警告,而是发出两个警告(“并非所有枚举值都在switch中处理”和“控件到达末尾非无效函数”),将另一个值添加到枚举中。

请记住,编译器诊断没有以任何方式进行标准化-编译器可以自由地报告符合规范的代码警告,并报告格式错误的程序的警告(并进行编译!)。

答案 1 :(得分:3)

您必须谨记,在C ++ enum中,它们似乎并不是真正的样子。它们只是受某些约束的int,并且可以轻松地采用除指示的值以外的其他值。考虑以下示例:

#include <iostream>

enum class MyEnum {
  A = 1,
  B = 2
};

int main() {
  MyEnum m {}; // initialized to 0
  switch(m) {
    case MyEnum::A: return 0;
    case MyEnum::B: return 0;
  }
  std::cout << "skipped all cases!" << std::endl; 
}

解决方法是将default放在assert(false)的情况下,如VTT指示above,或者(如果可以让所有人都保证没有任何值来自指示的集合,将会到达那里)使用特定于编译器的提示,例如GCC和clang上的__builtin_unreachable()

  switch(m) {
    case MyEnum::A: return 0;
    case MyEnum::B: return 0;
    default: __builtin_unreachable();
  }

答案 2 :(得分:1)

首先,您描述的是警告,而不是错误消息。编译器不需要发出此类警告,并且仍然可以成功编译您的代码-因为它在技术上是有效的。

实际上,大多数现代编译器都可以发出此类警告,但在其默认配置中则不会。使用gcc,可以选择将编译器配置为发出此类警告(例如,使用适当的命令行选项)。

在Linux下,这是“从不存在问题”的唯一原因是未对您选择的编译器进行配置(或与适当的命令行选项一起使用)以发出警告。

大多数编译器直接(在解析源代码期间)或通过分析该代码的某些内部表示来对代码进行大量分析。需要进行分析以确定代码是否存在可诊断的错误,以找出如何优化性能。

由于这种分析,即使代码没有可诊断的错误(即“足够正确”,C ++标准不需要诊断),大多数编译器也可以并且确实能够检测出可能存在问题的情况。

在这种情况下,取决于编译器进行分析的方式,编译器可能会得出许多不同的结论。

  • 有一个switch。原则上,可以执行switch语句之后的代码。
  • 切换后的代码到达函数末尾而没有return,并且该函数返回一个值。其结果是潜在的不确定行为。

如果编译器的分析到此为止(并且已将编译器配置为发出警告),则符合发出警告的条件。如果可以抑制警告,则需要进一步分析。确定e的所有可能值都由case表示,并且所有情况都有return语句。事实是,出于各种原因,编译器供应商可能会选择不进行此类分析,因此也就不会抑制警告。

  • 进行更多分析会增加编译时间。供应商争相声称自己的编译器速度更快,因此,不进行任何分析因此有利于缩短编译时间;
  • 即使代码实际上是正确的,编译器供应商也可能认为最好标记潜在的问题。在给出无关紧要的警告或不发出警告的选择之间,供应商可能更愿意发出多余的警告。

在这两种情况下,都不会进行确定警告可以被抑制的分析,因此不会抑制该警告。编译器根本不会做足够的分析来确定该函数的所有执行路径都遇到return语句。

最后,您需要将编译器警告视为潜在问题的标志,然后对潜在问题是否值得打扰做出明智的决定。您在此处的选择包括禁止显示警告(例如,使用导致禁止显示警告的命令行选项),修改代码以防止显示警告(例如,在return之后添加switch和/或default中返回的switch情况)。

答案 3 :(得分:0)

在省略return语句时应该非常小心。这是未定义的行为:

  

9.6.3 return语句[stmt.return]

     

从构造函数,析构函数或具有cv void返回类型的函数的末尾流出就等于没有操作数的返回。否则,从main(6.6.1)以外的函数的末尾流出会导致不确定的行为。

由于所有有效的枚举值(在这种情况下,范围为0..1 [0..(2 ^ M - 1)]M = 1中)都在{{1}中处理,因此可能会认为此代码很好},但在进入UB区域之前,不需要编译器进行任何特殊的可达性分析来解决此问题。

此外,SergeyA's answer的示例表明,这种代码是连续炸弹:

switch

只需添加第三个枚举成员(并在class myClass { protected: enum class myEnum{ a, b, c }; int fun(myClass::myEnum e); }; int myClass::fun(myClass::myEnum e) { switch (e){ case myEnum::a:{ return 0; } case myEnum::b:{ return 1; } case myEnum::c:{ return 2; } } } 中进行处理),有效枚举器值的范围就会扩展到switch0..3[0..(2 ^ M - 1)])和{ {3}}不会有任何抱怨,即使将3传递给该函数也会错过该切换,因为编译器也不需要报告UB。

因此,经验法则是以所有路径以M = 2 returnthrow函数结尾的方式编写代码。在这种特殊情况下,我可能会为未处理的枚举数写一个带有[[noreturn]]的单个return语句:

assertion