我正在使用旧版gcc(如果我没记错的话,是7.)开发LINUX中的应用程序。 最近,我尝试在Windows上运行相同的应用程序。 在Windows上,我将MinGW用作编译器(带有gcc 8.1.0)。
在Windows上编译我的应用程序时遇到了此错误消息:
警告:控制到达非空函数[-Wreturn-type]
的末尾
代码类似于以下内容:
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;
}
}
}
我理解错误消息的含义,我只是想知道为什么它在LINUX中从来不是问题。
这段代码真的是一个问题,我是否必须添加一些虚拟的return语句?
此函数的分支是否会导致没有return语句?
答案 0 :(得分:4)
这是g ++静态分析器的缺点。没有得到所有枚举值都在switch语句中正确处理的事实。
您可以在此处https://godbolt.org/z/LQnBNi中注意到,clang不会对其当前形状的代码发出任何警告,而是发出两个警告(“并非所有枚举值都在switch中处理”和“控件到达末尾非无效函数”),将另一个值添加到枚举中。
请记住,编译器诊断没有以任何方式进行标准化-编译器可以自由地报告符合规范的代码警告,并报告格式错误的程序的警告(并进行编译!)。
答案 1 :(得分:3)
您必须谨记,在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)
的情况下,如VTT指示above,或者(如果可以让所有人都保证没有任何值来自指示的集合,将会到达那里)使用特定于编译器的提示,例如GCC和clang上的__builtin_unreachable()
:
switch(m) {
case MyEnum::A: return 0;
case MyEnum::B: return 0;
default: __builtin_unreachable();
}
答案 2 :(得分:1)
首先,您描述的是警告,而不是错误消息。编译器不需要发出此类警告,并且仍然可以成功编译您的代码-因为它在技术上是有效的。
实际上,大多数现代编译器都可以发出此类警告,但在其默认配置中则不会。使用gcc,可以选择将编译器配置为发出此类警告(例如,使用适当的命令行选项)。
在Linux下,这是“从不存在问题”的唯一原因是未对您选择的编译器进行配置(或与适当的命令行选项一起使用)以发出警告。
大多数编译器直接(在解析源代码期间)或通过分析该代码的某些内部表示来对代码进行大量分析。需要进行分析以确定代码是否存在可诊断的错误,以找出如何优化性能。
由于这种分析,即使代码没有可诊断的错误(即“足够正确”,C ++标准不需要诊断),大多数编译器也可以并且确实能够检测出可能存在问题的情况。
在这种情况下,取决于编译器进行分析的方式,编译器可能会得出许多不同的结论。
switch
。原则上,可以执行switch
语句之后的代码。return
,并且该函数返回一个值。其结果是潜在的不确定行为。如果编译器的分析到此为止(并且已将编译器配置为发出警告),则符合发出警告的条件。如果可以抑制警告,则需要进一步分析。确定e
的所有可能值都由case
表示,并且所有情况都有return
语句。事实是,出于各种原因,编译器供应商可能会选择不进行此类分析,因此也就不会抑制警告。
在这两种情况下,都不会进行确定警告可以被抑制的分析,因此不会抑制该警告。编译器根本不会做足够的分析来确定该函数的所有执行路径都遇到return
语句。
最后,您需要将编译器警告视为潜在问题的标志,然后对潜在问题是否值得打扰做出明智的决定。您在此处的选择包括禁止显示警告(例如,使用导致禁止显示警告的命令行选项),修改代码以防止显示警告(例如,在return
之后添加switch
和/或default
中返回的switch
情况)。
答案 3 :(得分:0)
在省略return语句时应该非常小心。这是未定义的行为:
9.6.3 return语句[stmt.return]
从构造函数,析构函数或具有cv void返回类型的函数的末尾流出就等于没有操作数的返回。否则,从main(6.6.1)以外的函数的末尾流出会导致不确定的行为。
由于所有有效的枚举值(在这种情况下,范围为0..1
[0..(2 ^ M - 1)]
和M = 1
中)都在{{1}中处理,因此可能会认为此代码很好},但在进入UB区域之前,不需要编译器进行任何特殊的可达性分析来解决此问题。
此外,SergeyA's answer的示例表明,这种代码是连续炸弹:
switch
只需添加第三个枚举成员(并在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;
}
}
}
中进行处理),有效枚举器值的范围就会扩展到switch
(0..3
和[0..(2 ^ M - 1)]
)和{ {3}}不会有任何抱怨,即使将3传递给该函数也会错过该切换,因为编译器也不需要报告UB。
因此,经验法则是以所有路径以M = 2
return
或throw
函数结尾的方式编写代码。在这种特殊情况下,我可能会为未处理的枚举数写一个带有[[noreturn]]
的单个return语句:
assertion