我确信所有三个函数都有未定义的行为。
对于坚持f3
不是 UB(甚至f1
/ f2
)的人:请您尝试运行此代码:
#include <iostream>
const char* const& f1() { return "hello1"; }
const char* const& f2() { return static_cast<const char*>("hello2"); }
const char* const& f3() { const char* const& r = "hello3"; return r; }
int main()
{
using namespace std;
//#define F f1
//#define F f2
#define F f3
const char* const& ret = F();
cerr << ret;
cerr << ",";
cerr << ret;
return 0;
}
(我使用cerr
而不是cout
立即冲洗。您可以更改cerr
为并在第二个输出之后cout
添加一个。)cout << flush;
ret
在我的 GCC 上,这是我打印的内容:
- with
f1
:(hello1,8??q?
逗号后的一些随机字符)
- with
f2
:(hello2,8j?y5
逗号后的一些随机字符)
- with
f3
:(hello3,,
逗号后的第二个逗号)
对我来说,这看起来很像UB......
(注意:如果我删除任何一个,const&
那么它“有效”。const&
真正删除的当然是返回类型中的一个。)
我认为那是因为发生的f3
事情是这样的:
const char* const& f3()
{
const char* __tmp001 = &("hello3"[0]); // "array decaying"
const char* const& r = __tmp001;
return r;
}
实际上,字符串文字"hello3"
不是a ,而是const char*
a (static) const char [7]
。在代码const char* const& r = "hello3";
中,引用不能直接绑定到这个char数组,因为它没有相同的类型,所以编译器必须创建一个临时的char指针(创建在堆栈上),通过隐式转换(array-to-pointer衰减) 引用绑定到的 ( demo )。这个临时的生命周期const char*
被“延长”到引用的生命周期r
,因此不会在第一个分号处结束,而是在函数返回时结束(演示和输出关闭所有优化)。所以f3
返回一个“悬空引用”. 在我的测试输出代码中,任何覆盖堆栈的后续操作都会使 UB 可见。
在 jalf 的评论之后编辑:我意识到“它在第二个输出上打印垃圾”不是 UB 的证明。带有 UB 的程序也可以完全按预期工作,或者崩溃,或者什么也不做,或者其他什么。但是,尽管如此,我不相信一个定义明确的程序(没有 UB)会打印这样的垃圾......