如果编译器是未定义的行为,为什么会发出关于返回对本地堆栈变量的引用的警告?

时间:2011-07-19 10:40:21

标签: c++ compiler-construction standards

C ++标准规定返回对局部变量(在堆栈上)的引用是未定义的行为,那么为什么许多(如果不是全部)当前编译器会发出警告?

struct A{
};

A& foo()
{
    A a;
    return a; //gcc and VS2008 both give this a warning, but not a compiler error
}

如果编译器给出错误而不是警告此代码会不会更好?

允许使用警告编译代码有什么好处吗?

请注意,这不是一个const引用,它可能会延长临时生命周期的生命周期。

7 个答案:

答案 0 :(得分:22)

从编译器的角度来看,验证是否返回对临时的引用几乎是不可能的。如果标准规定要将其诊断为错误,那么编写编译器几乎是不可能的。考虑:

bool not_so_random() { return true; }
int& foo( int x ) {
   static int s = 10;
   int *p = &s;
   if ( !not_so_random() ) {
      p = &x;
   }
   return *p;
}

上述程序运行正确且安全,在我们当前的实现中,保证foo将返回对static变量的引用,这是安全的。但是从编译器的角度来看(并且在单独编译的情况下,not_so_random()的实现无法访问,编译器无法知道程序是否格式正确。

这是一个玩具示例,但您可以想象具有不同返回路径的类似代码,其中p可能会引用返回*p的所有路径中的不同长寿命对象。

答案 1 :(得分:10)

未定义的行为不是编译错误,它不是一个格式良好的C ++程序。并非每个不正确的程序都是不可编辑的,它只是不可预测的。我敢打赌,原则上计算机无法确定给定的程序文本是否是格式良好的C ++程序。

您始终可以将-Werror添加到gcc以使警告终止编译并显示错误!

添加另一个最喜欢的SO主题:您是否希望++i++导致编译错误?

答案 2 :(得分:8)

如果返回指向本地内部函数的指针/引用,只要不取消引用从函数返回的指针/引用,行为就可以很好地定义

只有在退出指针时才会显示未定义的行为。

是否是未定义的行为取决于调用函数的代码而不是函数本身。

因此,在编译函数时,编译器无法确定行为是未定义还是定义良好。它能做的最好的事情是警告你一个潜在的问题而且确实存在!

代码示例:

#include <iostream>

struct A
{ 
   int m_i;
   A():m_i(10)
   {

   } 
};  
A& foo() 
{     
    A a;
    a.m_i = 20;     
    return a; 
} 

int main()
{
   foo(); //This is not an Undefined Behavior, return value was never used.

   A ref = foo(); //Still not an Undefined Behavior, return value not yet used.

   std::cout<<ref.m_i; //Undefined Behavior, returned value is used.

   return 0;
}

参考C ++标准:
第3.8节

在对象的生命周期开始之前但是在对象将占用的存储之后已经被分配34)或者,在对象的生命周期结束之后和对象的存储之前占用被重用或释放,任何指向对象所在或所在的存储位置的指针都可以使用,但只能以有限的方式使用。这样的指针指的是已分配的存储(3.7.3.2),并使用该 指针好像指针是type void*一样,定义得很好。 这样的指针可以被解除引用但是所得到的左值可以仅以有限的方式使用,如下所述。如果对象将是或具有非平凡析构函数的类类型,并且指针用作delete-expression的操作数,则程序具有未定义的行为。如果对象将是或者是非POD类类型,则在以下情况下,程序具有未定义的行为:

- .......

答案 3 :(得分:3)

因为标准不限制我们。

如果你想自己拍摄,你可以做到!

然而,让我们看一下它可能有用的例子:

int &foo()
{
    int y;
}

bool stack_grows_forward()
{
    int &p=foo();
    int my_p;
    return &my_p < &p;
}

答案 4 :(得分:2)

编译人员不应拒绝编译程序,除非标准规定允许他们这样做。否则,移植程序会更加困难,因为它们可能无法使用不同的编译器进行编译,即使它们符合标准。

考虑以下功能:

int foobar() {
    int a=1,b=0;
    return a/b;
}

任何体面的编译器都会检测到我除以零,但它不应该拒绝代码,因为我可能实际上想要触发SIG_FPE信号。

正如大卫罗德里格斯指出的那样,有些案例是不可判定的,但也有一些案例不是。该标准的某些新版本可能描述了必须/允许编译器拒绝程序的某些情况。这将要求标准对要执行的静态分析非常具体。

Java标准实际上指定了一些用于检查非void方法总是返回值的规则。不幸的是,我还没有阅读足够的C ++标准来了解允许编译器做什么。

答案 5 :(得分:0)

您还可以返回对静态变量的引用,该变量将是有效代码,因此代码必须能够编译。

答案 6 :(得分:0)

依靠这个实际上是非常糟糕的做法,但我相信在许多情况下(并且这绝不是一个好的赌注),如果在时间{{1}之间没有调用任何函数,那么内存引用仍然有效。返回以及调用函数使用其返回值的时间。在这种情况下,堆栈的那个区域将没有机会被覆盖。

在C和C ++中,您可以选择通过指针算法访问内存的任意部分(当然是在进程的内存空间内),那么为什么不允许构建对任何一个人选择的引用的可能性?

相关问题