【问题标题】:Why "not all control paths return a value" is warning and not an error?为什么“并非所有控制路径都返回值”是警告而不是错误?
【发布时间】:2009-11-14 17:56:23
【问题描述】:

我试图回答this 的问题。正如接受的答案所建议的那样,该代码的问题在于并非所有控制路径都返回一个值。我在 VC9 编译器上尝试了这段代码,它给了我同样的警告。我的问题是为什么只是警告而不是错误?另外,如果不返回值的路径被执行,函数将返回什么(它必须返回一些东西)?它只是堆栈顶部的任何东西,还是再次出现可怕的未定义行为?

【问题讨论】:

  • 专业上大多数地方都将警告视为错误。因此,大多数编译器都有一个标志,告诉编译器将警告视为错误。我从来没有做过一份工作(除了处理遗留代码),我们没有打开这个标志并且在签入之前坚持 0 个警告。
  • 还有 0 个错误,以防万一认为这不是暗示。

标签: c++ compiler-warnings


【解决方案1】:

无法从具有非void 返回类型的函数返回值会导致未定义的行为,但这不是语义错误。

据我所知,其原因主要是历史原因。

C 最初没有void,隐含的int 意味着大多数函数返回int,除非明确声明返回其他内容,即使无意使用返回值。

这意味着很多函数返回一个 int 但没有显式设置返回值,但这没关系,因为调用者永远不会为这些函数使用返回值。

有些函数确实返回了一个值,但使用了隐式的int,因为int 是一个合适的返回类型。

这意味着void之前的代码有很多名义上返回int但可以声明为返回void的函数以及许多其他应该返回int但没有明确方法的函数区别。在任何阶段对所有非void 函数的所有代码路径强制执行return 都会破坏遗留代码。

还有人认为函数中的某些代码路径可能无法访问,但这可能不容易从简单的静态分析中确定,那么为什么要强制执行不必要的return

【讨论】:

  • 如果您想到switch,则可以轻松解释静态分析,而强烈建议您提供default,这不是必需的,也可能没有必要......但是如何编译器应该知道吗?另一方面......你应该在编译时将警告视为错误,这样会更好。
  • 这是一个非常有用的警告,当它真的是一个错误并且你忘记了返回时,它已经让我多次发现并警告至少对于这个特定的错误,确保你不会忽视它。
【解决方案2】:

我猜这只是一个警告,因为编译器不能总是 100% 确定有可能不返回。

即如果你有:


-= source1.c =-
int func()
{
    if(doSomething())
    {
       return 0;
    }
}

-= source2.c =-
int doSomething()
{
    return 1;
}

在这种情况下,编译器可能无法知道它总是会返回,但你知道。当然,依靠了解外部代码的工作原理,这是一种糟糕的编程实践。

至于实际返回的内容取决于平台。在 x86 ABI 上,EAX 用于返回值(最多 32 位),因此它将返回放置在该寄存器中的内容(可能是其他东西的返回值、临时值或总垃圾)。

【讨论】:

  • 在这种情况下,为什么还要有 if 语句?这段代码是错误的,它应该无法编译。由于历史原因、向后兼容性等原因,这是一个警告。现在 C++ 应该删除它并将其作为错误处理。
【解决方案3】:

从技术上讲,如果您调用一个函数并且该函数总是抛出异常,则不能保证一定会出错。例如这里是一些伪代码,你知道 raiseError 总是抛出。

MyClass func( params )
{
     if( allIsValid() )
     {
       return myObject;
     }
     else
     {
        raiseError( errorInfo );
     }
}

如果编译器看不到 raiseError 的实现,它就不会知道该函数将要抛出。所以实际上这里实际上没有未定义的行为。当然,最好在此处使编译器静音,您可以通过在 raiseError 之后编写“虚拟”返回语句或虚拟“抛出”来做到这一点。我称它们为“哑巴”,因为它们在现实中永远无法触及。 (如果你真的坚持,你也可以压制警告)。但是没有错误或未定义的行为。

【讨论】:

    【解决方案4】:

    这不是错误的另一个原因

    以下内容会给你同样的警告,因为编译器希望你从 catch 块中返回一些东西,即使你在那里扔东西

    int foo(){
       try{
          return bar(0);
       } catch(std::exception& ex){
          //do cleanup
          throw ex;
       }
    }
    
    int bar(unsigned int i){
       if(i == 0){
          throw std::string("Value must be greater than 0");
       } else{
          return 0;
       }
    }
    

    【讨论】:

    • 它不希望你在抛出时返回 - 至少 VC9 没有,我不知道其他编译器。但是,如果您正在调用一个总是抛出的函数(当然,您有一个编译错误消息和抛出的例程)并且您调用它并且编译器无法“看到”它,您会收到警告。因此我的解决方案如下。
    【解决方案5】:

    另一个例子,某些控制路径不返回值可能是可以的:

    enum E : int {A, B};
    
    int foo(E e) {
      switch (e) {
        case A: return 30;
        case B: return 50;
      }
    }
    

    e 可能不是AB,但这意味着它始终是这些值之一。如果是这样,那么代码很好,没有问题。将此警告变为强制性错误将需要不必要的、“无法访问”的混乱。

    如果您希望警告仍然是错误,您可以配置您的编译器以使用 /WX 或 -Werror 之类的标志来执行此操作。当然,您应该注意,不同的编译器可能会对无法访问的内容做出不同的决定,因此您可能会为不同的编译器修复不同的东西。

    【讨论】:

      【解决方案6】:

      这不是错误,因为它可能是预期的行为。例如,一些加密库使用未初始化的本地数据作为种子的步骤。由于返回值保存在调用约定和平台特定的位置,这可能有助于一些不寻常的(如上述)情况。在这种情况下,函数返回用于返回返回值的寄存器中剩余的任何内容。

      【讨论】:

      • 你能举一个使用未初始化数据作为种子的加密库的具体例子吗?一个称职的库听起来不太可能这样做,因为未初始化的数据在加密术语中绝不是随机的。
      • 真的吗?什么加密库可以做到这一点?依赖加密库中未定义的行为似乎是一个巨大的潜在安全漏洞。
      • 我会更喜欢正确生成的随机数,而不是未初始化的数据作为种子。如果您使用 MSVC++ 调试器,您会经常看到未初始化的数据具有固定值(在程序 0xCCCCCCCC、0xCDCDCDCD 的调试版本中)。
      • @Shailesh:我不喜欢未初始化的数据结构。我才想起来,曾经有段时间有些程序员依赖这个东西……
      • @Shailesh:不幸的是,很难正确生成随机数 (springerlink.com/content/kkpghbg30r1xce4m)。我还记得一些密码学家通过使用该芯片上的一些其他硬件(可能是声卡,我不记得)来攻击他们芯片组上的英特尔硬件 RNG,以便他可以保持该区域的温度足够高,以使 RNG 产生很少的熵(因为它依赖于热噪声)......
      【解决方案7】:

      考虑以下场景:

      UINT GenderID(GENDER gender)
      {
          switch(gender)
          {
          case MALE:
              return MALE_ID;
          case FEMALE:
              return FEMALE_ID;
          }
      // default not required because [GENDER] in our 'Matrix' CAN be either M or F
      }
      

      C++ 编译器应该让您按照自己的方式拥有“矩阵”;因此它不是错误。

      【讨论】:

      • 您基本上是在说这不是错误,因为您想出了一个您认为应该可行的人为示例。
      • @Andy as 这不是错误 - 我不必证明这一点。此外,我的示例在很大程度上是一个真实世界的示例——它的重点是呈现一种情况——将其声明为错误本身就是一个错误。
      • 你不回答 为什么 首先是允许的,这是 OP 想知道的。您的回答是重言式,仅提供了 OP 所询问内容的另一个示例,它没有回答问题。我可以从你的回答声明中收集到的唯一信息是,你认为代码应该编译,因为它应该“让你随心所欲”。
      • @Andy 先生,我相信 - 这就是答案:'编译器应该让你随心所欲';虽然它可以发出警告 - 它确实如此。
      • 现在您正在谈论您的意见。用 C# 编写的没有返回的代码(当方法不是 void 时)会生成编译器错误。 IMO,未定义的返回值的用处远远超过它在程序中引起的错误,我永远不会让这样的代码通过代码审查。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-08
      • 2014-12-06
      • 1970-01-01
      • 1970-01-01
      • 2014-11-15
      相关资源
      最近更新 更多