代码之家  ›  专栏  ›  技术社区  ›  user7431005

在使用switch语句的函数中没有返回

  •  4
  • user7431005  · 技术社区  · 6 年前

    我正在LINUX中开发一个应用程序,它使用的是较旧的gcc版本(如果我没记错的话,可能是7.0版本)。 最近我尝试在Windows上运行相同的应用程序。 在Windows上,我使用MinGW作为编译器(使用gcc8.1.0)。

    代码类似于以下内容:

    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;
            }
        }
    }
    

    这段代码真的有问题吗?我需要添加一些伪返回语句吗?

    这个函数是否有一个分支会导致no return语句?

    4 回复  |  直到 6 年前
        1
  •  4
  •   SergeyA    6 年前

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

    你可以注意到这里 https://godbolt.org/z/LQnBNi 当向枚举中添加另一个值时,该叮当声不会对当前形状中的代码发出任何警告,并发出两个警告(“并非所有枚举值都在switch中处理”和“控件到达非void函数的结尾”)。

    请记住,编译器诊断并不是以任何方式标准化的—编译器可以自由地报告一致性代码的警告,并报告警告(和编译!)对于一个错误的程序。

        2
  •  4
  •   Andrzej    6 年前

    你必须记住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) above __builtin_unreachable() 关于GCC和clang:

      switch(m) {
        case MyEnum::A: return 0;
        case MyEnum::B: return 0;
        default: __builtin_unreachable();
      }
    
        3
  •  1
  •   Peter    6 年前

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

    在linux下,这是“从来没有问题”的唯一原因是您选择的编译器没有配置(或与合适的命令行选项一起使用)来发出警告。

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

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

    • switch . 原则上,在 转换
    • 开关到达函数末尾后的代码 return

    如果编译器的分析进行到这一步(并且编译器被配置为对此类事件发出警告),则满足发出警告的条件。如果可以抑制警告,则需要进一步分析,例如,确定 e 由一个 case ,所有案例都有 返回 声明。问题是,编译器供应商可能会出于各种原因选择不进行此类分析,因此不会抑制警告。

    • 编译器供应商可能认为最好标记潜在的问题,即使代码实际上是正确的。如果在给出无关警告或不对某些事情发出警告之间做出选择,供应商可能更愿意给出无关警告。

    在这两种情况下,都不会进行分析以确定可以抑制警告,因此不会抑制警告。编译器将没有做足够的分析来确定通过函数的所有执行路径都遇到错误 返回 声明。

    最后,您需要将编译器警告视为潜在问题的标志,然后对潜在问题是否值得关注做出明智的决定。这里的选项包括抑制警告(例如,使用命令行选项使警告被抑制)、修改代码以防止警告(例如,添加 返回 转换 和/或 default 返回)。

        4
  •  0
  •   user7860670    6 年前

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

    从构造函数、析构函数或具有cv void返回类型的函数的结尾流出,相当于没有操作数的返回。否则,从main(6.6.1)以外的函数结尾流出会导致未定义的行为。

    考虑到这段代码是好的,因为所有有效的枚举值(在本例中是在 0..1 [0..(2 ^ M - 1)] 具有 M = 1 switch 然而,在跳入UB区域之前,编译器不需要执行任何特定的可达性分析来解决这个问题。

    此外,来自 SergeyA's answer 显示这类代码是一个直接的定时炸弹:

    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;
            }
        }
    }
    

    只需添加第三个枚举成员(并在 转换 0..3 ( 具有 M = 2 clang happily accepts it 没有任何抱怨,即使传递3到这个函数将错过开关,因为编译器也不需要报告UB。

    因此,经验法则是以所有路径都以 return throw [[noreturn]] assertion 对于未处理的枚举数值:

    int myClass::fun(myClass::myEnum e) {
        int result{};
        switch (e){
            case myEnum::a:{
                result = 0;
                break;
            }
            case myEnum::b:{
                result = 1;
                break;
            }
            default:
            {
               assert(false);
               break;
            }
        }
        return result;
    }