C++ 为什么;并非所有控制路径都返回一个值";警告不是错误吗?

C++ 为什么;并非所有控制路径都返回一个值";警告不是错误吗?,c++,compiler-warnings,C++,Compiler Warnings,我试图回答这个问题。正如公认的答案所建议的,该代码的问题在于并非所有控制路径都返回值。我在VC9编译器上尝试了这段代码,它给了我同样的警告。我的问题是为什么这只是一个警告而不是一个错误?另外,如果执行了不返回值的路径,函数将返回什么(它必须返回什么)?它只是堆栈顶部的东西,还是可怕的未定义行为再次出现?这不是错误,因为它可能是预期的行为。例如,某些加密库使用未初始化的本地数据作为种子设定的步骤。由于返回值保存在调用约定和特定于平台的位置,因此在某些异常(如上述)情况下可能会有所帮助。在这种情况下

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

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

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

i、 e.如果你有:


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

-= source2.c =-
int doSomething()
{
    return 1;
}
在本例中,编译器可能无法知道它将始终返回,但您确实知道。当然,依靠了解外部代码是如何工作的,这是一种糟糕的编程实践


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

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

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

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

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

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

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


还有一种观点认为,函数中的某些代码路径可能无法访问,但这可能不容易从简单的静态分析中确定,因此为什么要强制执行不必要的
返回

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

下面将给出相同的警告,因为编译器希望您从catch块返回一些内容,即使您正在抛出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;
   }
}

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

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

如果编译器看不到raiseError的实现,它将不知道函数将抛出。所以这里实际上没有未定义的行为。当然,在这里让编译器静音是很好的,您可以在raiseError之后编写一个“dummy”返回语句,或者编写一个伪“throw”。我称他们为“傻瓜”,因为他们在现实中永远无法触及。(如果您确实坚持,也可以取消显示警告)。但是,没有错误或未定义的行为。

请考虑以下情况:

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++编译器,应该<强>让你你的矩阵“你的方式”;因此,这不是一个错误。

另一个示例,在该示例中,某些控制路径可能不返回值:

enum E : int {A, B};

int foo(E e) {
  switch (e) {
    case A: return 30;
    case B: return 50;
  }
}
e
可能不是
A
B
,但这意味着它总是这些值之一。如果是这种情况,那么代码是好的,没有问题。将此警告变成强制错误将需要不必要的“无法到达”混乱


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

您能否举一个使用未初始化数据作为种子的加密库的具体示例?听上去,一个有能力的库不太可能做到这一点,因为未初始化的数据在加密术语中是随机的。真的吗?什么加密库可以做到这一点?依赖于加密库中未定义的行为似乎是一个巨大的潜在安全漏洞。我希望有一天能使用正确生成的随机数,而不是未初始化的数据作为种子。如果您使用MSVC++调试器,您会经常看到未初始化的数据具有固定值(在程序0xCCCC、0xCDCDCDCD的调试版本中)。@Shailesh:我不喜欢未初始化的数据结构。我只记得,有一次,一些程序员依赖于这个东西…@Shailesh:不幸的是,很难正确地生成随机数()。我还记得一些密码学者通过使用芯片上的其他硬件(也许是声卡,我不记得)攻击英特尔硬件RNG,这样他就可以保持那个区域的温度足够高,RNG产生很少的熵(因为它依赖于热噪声)。