32

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

4

7 回答 7

32

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

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

C 最初没有,void并且隐式int意味着大多数函数返回一个int除非明确声明返回其他内容,即使不打算使用返回值。

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

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

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

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

于 2009-11-14T18:16:20.450 回答
10

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

即如果你有:


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

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

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

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

于 2009-11-14T18:08:54.580 回答
6

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

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

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

于 2012-09-13T10:25:36.140 回答
1

这是它不是错误的另一个原因

以下内容会给你同样的警告,因为编译器希望你从 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;
   }
}
于 2012-08-15T22:50:44.020 回答
1

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

enum E : int {A, B};

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

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

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

于 2015-08-07T17:25:51.383 回答
-2

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

于 2009-11-14T18:02:53.067 回答
-2

考虑以下场景:

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++ 编译器应该让您拥有自己的“矩阵”;因此它不是错误。

于 2012-10-10T21:40:06.933 回答