为什么“并非所有控制路径都返回值”是警告而不是错误?

时间:2009-11-14 17:56:24

标签: c++ compiler-warnings

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

7 个答案:

答案 0 :(得分:30)

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

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

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

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

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

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

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

答案 1 :(得分:9)

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

即。如果你有:


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

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

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

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

答案 2 :(得分:6)

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

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

如果编译器看不到raiseError的实现,则不会知道该函数是否会抛出。所以实际上这里确实没有未定义的行为。当然,在这里使编译器静音是很好的,你可以在raiseError之后写一个“虚拟”返回语句,或者使用虚拟“throw”。我把它们称为“虚拟”,因为它们永远不会实现。 (如果你真的坚持,你也可以取消警告)。但是没有错误或未定义的行为。

答案 3 :(得分: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;
   }
}

答案 4 :(得分:1)

另一个例子,某些控制路径可能没有返回值:

enum E : int {A, B};

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

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

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

答案 5 :(得分:-2)

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

答案 6 :(得分:-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 ++编译器应该让你让你的'Matrix'成为你的方式;这不是错误。