#include <iostream>
int getID ( int k ) {
static int& r = k;
return r++;
}
int main()
{
int a = getID ( 10 );
int b = getID ( 10 );
std::cout << "a = " << a << ", b = " << b << std::endl;
return 0;
}
我不明白为什么这段代码会编译。
答案 0 :(得分:5)
该语言不会阻止您绑定对有限生命周期的对象的引用。这将使得很难使用参考文献。
(它确实阻止你绑定对temporaries的引用。但是函数的参数是l值,就像局部变量一样,所以它是允许的。)
但是,如果您编写代码,其中引用绑定到一个超出它的对象,并且您继续使用该引用,则会得到未定义的行为,不需要诊断,并且很可能是段错误。它与C
中的悬空指针基本相同。
在第二次调用时,您不将静态引用变量重新绑定到新临时变量。静态变量仅初始化一次,第一次时间调用该函数。所有后续调用都会有效跳过该行。
如果你想要一种能够为你捕捉到这样的错误的语言,并确保你不会因为悬挂引用而被咬,你可以看看Rust。生锈编译器有一个&#34;借用检查器&#34;它将检查您的引用,而不会像许多众所周知的垃圾收集语言那样强加运行时开销。但Rust没有静态变量,因此没有直接翻译该代码;)
在C ++中,我猜想上面的错误可能会被像Coverity这样的静态分析工具捕获,它会在扫描代码时对代码进行一些生命周期检查。但是C ++编译器不会为你做这件事,你需要使用第三方工具。
答案 1 :(得分:3)
静态引用如何引用局部变量k,它将在函数调用结束时消失。
完全有可能。您最终会得到一个悬空引用,这会导致未定义的行为。但static
部分在这里并不重要。
在第二次调用时,我们使用新变量重新初始化静态引用。 [...]
不,我们没有。 static
变量仅初始化一次。因此,在getID()
中第二次,我们仍然指的是之前的临时k
。然后我们增加,这是未定义的行为。 &#34;未定义行为的一个方面&#34;是&#34;代码看起来像是有效的。&#34;
考虑一种新类型:
struct WrappedInt {
~WrappedInt() {
i = 0;
}
int i;
};
并重写代码以改为使用它:
int getID ( WrappedInt k ) {
static WrappedInt& r = k;
return r.i++;
}
在这里,我们将看到,一旦临时超出范围,并且它明确地将其值清零,当我们重新读取它(使用我们的悬空参考)时,我们将返回0
:
int main()
{
int a = getID ( WrappedInt{10} ); // a == 10
int b = getID ( WrappedInt{10} ); // b == 0
std::cout << "a = " << a << ", b = " << b << std::endl;
return 0;
}