2014-10-07 68 views
2

假设你用C++编写了一个函数,但却心不在焉地忘记输入字return。在这种情况下会发生什么?我希望编译器会抱怨,或者一旦程序到达这一点,至少会出现分段错误。但是,实际发生的情况更糟糕:该程序发出垃圾。不仅如此,实际产量取决于优化的水平!下面是一些代码,说明此问题:在C++中缺少返回值和优化的不规则行为

#include <iostream> 
#include <vector> 

using namespace std; 

double max_1(double n1, 
     double n2) 
{ 
    if(n1>n2) 
    n1; 
    else 
    n2; 
} 

int max_2(const int n1, 
     const int n2) 
{ 
    if(n1>n2) 
    n1; 
    else 
    n2; 
} 

size_t max_length(const vector<int>& v1, 
      const vector<int>& v2) 
{ 
    if(v1.size()>v2.size()) 
    v1.size(); 
    else 
    v2.size(); 
} 

int main(void) 
{ 
    cout << max_1(3,4) << endl; 
    cout << max_1(4,3) << endl; 

    cout << max_2(3,4) << endl; 
    cout << max_2(4,3) << endl; 

    cout << max_length(vector<int>(3,1),vector<int>(4,1)) << endl; 
    cout << max_length(vector<int>(4,1),vector<int>(3,1)) << endl; 

    return 0; 
} 

这里就是我得到的,当我在不同的优化级别编译:

$ rm ./a.out; g++ -O0 ./test.cpp && ./a.out 
nan 
nan 
134525024 
134525024 
4 
4 
$ rm ./a.out; g++ -O1 ./test.cpp && ./a.out 
0 
0 
0 
0 
0 
0 
$ rm ./a.out; g++ -O2 ./test.cpp && ./a.out 
0 
0 
0 
0 
0 
0 
$ rm ./a.out; g++ -O3 ./test.cpp && ./a.out 
0 
0 
0 
0 
0 
0 

现在想象一下你正在试图调试功能MAX_LENGTH。在生产模式下,你会得到错误的答案,所以你在调试模式下重新编译,现在当你运行它时,一切正常。

我知道有很多方法可以完全避免这种情况下,加入适当的警告标志(-Wreturn-type),但我仍然有两个问题

  1. 为什么编译甚至同意编译功能,而不返回声明?传统代码需要此功能吗?

  2. 为什么输出取决于优化级别?

+3

1.证明一个函数没有返回所有路径并不容易。 2.这是未定义的行为,所以你不能指望任何特定的输出。 – juanchopanza 2014-10-07 13:42:46

+1

相关[为什么此C++代码片段编译(非void函数不返回值)](http://stackoverflow.com/q/20614282/1708801)。基本上,由于它是未定义的行为,你的结果是不可预知的,编译器在优化过程中利用UB而臭名昭着。在所有情况下很难察觉到这一点。 – 2014-10-07 13:42:59

+0

你注意警告吗?将-Werror标志传递给gcc,它不会编译。 – Slava 2014-10-07 13:48:52

回答

-1

你可能想看看这个答案here

的只是它是编译器可以让你没有return语句,因为有许多潜在的不同的执行路径,以确保每一个将退出在编译时返回可能会非常棘手,因此编译器会为您处理它。

请注意以下事项:

如果没有返回主端将始终返回0

如果另一个函数没有返回结束它总是在EAX寄存器中返回的最后一个值,通常也是最后一次声明

优化更改程序集级别上的代码。这就是为什么你会得到奇怪的行为,编译器正在修改你的代码,以便在执行一些事情时给予不同的最后值,从而返回值。

希望这有助于!

+1

如果另一个函数没有返回就结束了,这是未定义的行为。 – 2014-10-07 17:53:17

+0

没错,因为你不能保证执行的最后一条语句是eax寄存器中的内容,但它是返回时使用的寄存器,因此就是调用者函数抓取的内容。除非它是一个非整数类型,但是它是XMM或FPU堆栈或任何类型。所以是没有定义的,但通常是最后一个声明的结果。 – 2014-10-07 18:22:51

+1

不,这只是未定义的。这与eax寄存器无关。它可能是这种情况,它通常是您的机器上的eax寄存器和您的编译器的最后一个值,但我可以在每次发生这种情况时使编译器返回47,并且它将100%符合标准。 – 2014-10-07 18:59:35

9

这是undefined behavior脱落的值返回函数结束,这是包括在draft C++ standard部分`31年6月6日return语句它说:

流下的函数结束相当于没有 价值的回报;这会导致在返回值 函数中出现未定义的行为。

编译器不需要发出诊断,我们可以从部分1.4实现达标它说看到这一点:

这套诊断的规则,包括所有的句法和语义 规则本国际标准除含有 的那些规则外,明确标注“不需要诊断”或将 描述为导致“未定义行为”。

虽然编译器通常会尝试捕获大量未定义的行为并产生警告,但通常您需要使用正确的标志集。对于gccclang我发现下面的一组标志是有用的:

-Wall -Wextra -Wconversion -pedantic

和一般我会鼓励你把警告到使用-Werror错误。

编译器是臭名昭著在优化阶段趁着未定义行为,请参阅Finding Undefined Behavior Bugs by Finding Dead Code一些好的例子包括臭名昭著的Linux内核空指针检查除尘凡在处理这个代码:

struct foo *s = ...; 
int x = s->f; 
if (!s) return ERROR; 

gcc推断,因为ss->f;中被引用,并且由于取消引用空指针是未定义的行为,因此s不能为空,因此优化了下一行的if (!s)检查(复制自我的answer here)。

由于未定义的行为是不可预知的,那么在更积极的设置下,编译器在许多情况下会做更积极的优化,其中许多可能没有多少直观的意义,但是,嘿它是未定义的行为,所以你应该没有任何期望。

请注意,虽然有很多情况下编译器可以确定一个函数没有正确返回一般情况下,这是halting problem。在运行时自动执行此操作会带来违反不支付您不使用哲学的成本。尽管gccclang都实现了sanitizers来检查未定义行为,例如使用-fsanitize=undefined标志将在运行时检查未定义的行为。