g++ 编译⚠️returning reference to local temporary object 但是运行依然得到预期结果?

2022-04-04 16:35:40 +08:00
 chuanqirenwu

代码来自 C++ templates 第二版:

#include <cstring>
#include <iostream>

using std::cout;
using std::endl;

// maximum of two values of any type (call-by-reference)
template <typename T>
T const &max(T const &a, T const &b)
{
    cout << "T const &max(T const &a, T const &b) called" << endl;
    return b < a ? a : b;
}

// maximum of two C-strings (call-by-value)
char const *max(char const *a, char const *b)
{
    cout << "char const *max(char const *a, char const *b) called" << endl;
    cout << a << endl;
    cout << b << endl;
    return std::strcmp(b, a) < 0 ? a : b;
}

// maximum of three values of any type (call-by-reference)
template <typename T>
T const &max(T const &a, T const &b, T const &c)
{
    return max(max(a, b), c); // error if max(a,b) uses call-by-value
}

int main()
{
    char const *s1 = "frederic";
    char const *s2 = "anica";
    char const *s3 = "lucas";
    auto m2 = ::max(s1, s2, s3); // run-time ERROR
    //!!! m2 已经是一个 dangling reference ,但是仍然得到预期结果?
    cout << m2 << endl;
}

不理解的地方在最后一行,m2 已经是一个 dangling reference ,但是为什么输出仍然得到预期结果?

编译使用的是 g++:

$ g++ --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 12.0.0 (clang-1200.0.31.1)
Target: x86_64-apple-darwin21.1.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

编译输出:

$ g++ test2.cpp -o test2 -std=c++11    
test2.cpp:28:12: warning: returning reference to local temporary object [-Wreturn-stack-address]
    return max(max(a, b), c); // error if max(a,b) uses call-by-value
           ^~~~~~~~~~~~~~~~~
test2.cpp:36:17: note: in instantiation of function template specialization 'max<const char *>' requested here
    auto m2 = ::max(s1, s2, s3); // run-time ERROR
                ^
1 warning generated.

运行结果:

char const *max(char const *a, char const *b) called
frederic
anica

char const *max(char const *a, char const *b) called
frederic
lucas

lucas
2066 次点击
所在节点    C++
14 条回复
nightwitch
2022-04-04 16:38:47 +08:00
undefined behaviour
可能出现任意的结果
chuanqirenwu
2022-04-04 16:43:15 +08:00
@nightwitch 意思是 g++ 下将这个 undefined behavior 定义成了最接近预期结果的 behavior 吗?从运行结果来看,就是得到的预期的输出:lucas
nightwitch
2022-04-04 17:00:36 +08:00
ub 的结果是不能预测的。
也许你在多写几行或者换个编译参数他结果就变了。
不用去深究它,也不要依赖你观察到的表现
macrorules
2022-04-04 17:04:19 +08:00
@nightwitch 怎么看出是 UB 啊?
nightwitch
2022-04-04 17:08:25 +08:00
@macrorules
https://en.cppreference.com/w/cpp/language/reference
翻到最下面 “Accessing such a reference is undefined behavior. ”
macrorules
2022-04-04 17:08:41 +08:00
如果你把 lucas 改成 eucas ,就会报错,因为 max(a, b) 的结果是一个临时变量,调用栈收缩之后就没了
chuanqirenwu
2022-04-04 17:21:50 +08:00
@macrorules max(max(a, b), c) 整个返回应该都是临时变量,因此跟参数的值应该没有关系。
icylogic
2022-04-04 18:19:01 +08:00
https://godbolt.org/z/PbMf5Ps8K

a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such return statement always returns a dangling reference.
yanqiyu
2022-04-04 18:43:51 +08:00
就算返回了临时变量的引用,但是也不意味着临时变量引用对应的地址会挪作他用,一般来说紧接着就用了而未进行压栈也大概率展示用不到
phiysng
2022-04-04 23:13:40 +08:00
@chuanqirenwu 未定义就是真的未定义,不存在`定义成了最接近预期结果的 behavior `
FrankHB
2022-04-08 18:43:55 +08:00
能运行也是 UB 的一种。
但是应该强调,返回临时变量的引用不一定就 UB ,这里只有限定返回局部自动对象的引用时才是。否则,这会显著干扰一些问题的理解,例如:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67795
FrankHB
2022-04-08 19:19:22 +08:00
@FrankHB 修正:返回非静态存储期的临时对象的引用。
和 @nightwitch 指出的来源一样,返回自动对象的引用是悬空引用。这里的自动对象是一个被声明的局部变量。
但是仔细看了下,OP 的代码中并不是这种情况。
问题出在 max(max(a, b), c)返回的是 const char*类型的右值,通过 temporary materialization conversion 初始化一个临时对象并初始化 T const&[T=const char*]类型的返回值,在 return 外临时对象被销毁,所以返回悬空引用。
注意这不涉及 C++意义上的变量。使用 g++时,中文警告[-Wreturn-local-addr]会把它叫做“临时变量”,这是技术上错的;而原文 returning reference to temporary 是对的,虽然过时( ISO C++17 前就直接叫 temporary ,不叫 temporary object )。
注意这里实际上用的是软链接过去的 Apple clang++,警告的选项不一样。
因为不是被声明变量,所以说 local (指的是作用域)也是技术上错的。
OP 的注释也是错的,悬空引用是=右边的部分;而被声明的变量 m2 就不是引用,因为用的是 auto 而不是 auto&。
chuanqirenwu
2022-04-08 22:07:10 +08:00
@FrankHB 谢谢!还是不理解,为什么说 m2 不是引用呢?返回类型不是 `T const &` 吗?
FrankHB
2022-04-09 11:16:22 +08:00
@chuanqirenwu 声明的类型通过初值符的类型推断,不保证相同。和在函数模板的参数列表里写不带&的参数类型规则相同,占位符不会被推断为引用类型,于是声明的 m2 不是引用类型的变量。如果要保留引用,可以 auto&或 auto&&之类。
http://www.eel.is/c++draft/dcl.spec.auto#dcl.type.auto.deduct-3

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/844896

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX