代码来自 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
1
nightwitch 2022-04-04 16:38:47 +08:00
undefined behaviour
可能出现任意的结果 |
2
chuanqirenwu OP @nightwitch 意思是 g++ 下将这个 undefined behavior 定义成了最接近预期结果的 behavior 吗?从运行结果来看,就是得到的预期的输出:lucas
|
3
nightwitch 2022-04-04 17:00:36 +08:00
ub 的结果是不能预测的。
也许你在多写几行或者换个编译参数他结果就变了。 不用去深究它,也不要依赖你观察到的表现 |
4
macrorules 2022-04-04 17:04:19 +08:00
@nightwitch 怎么看出是 UB 啊?
|
5
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. ” |
6
macrorules 2022-04-04 17:08:41 +08:00
如果你把 lucas 改成 eucas ,就会报错,因为 max(a, b) 的结果是一个临时变量,调用栈收缩之后就没了
|
7
chuanqirenwu OP @macrorules max(max(a, b), c) 整个返回应该都是临时变量,因此跟参数的值应该没有关系。
|
8
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. |
9
yanqiyu 2022-04-04 18:43:51 +08:00 1
就算返回了临时变量的引用,但是也不意味着临时变量引用对应的地址会挪作他用,一般来说紧接着就用了而未进行压栈也大概率展示用不到
|
10
phiysng 2022-04-04 23:13:40 +08:00 1
@chuanqirenwu 未定义就是真的未定义,不存在`定义成了最接近预期结果的 behavior `
|
11
FrankHB 2022-04-08 18:43:55 +08:00
能运行也是 UB 的一种。
但是应该强调,返回临时变量的引用不一定就 UB ,这里只有限定返回局部自动对象的引用时才是。否则,这会显著干扰一些问题的理解,例如: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67795 |
12
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&。 |
13
chuanqirenwu OP @FrankHB 谢谢!还是不理解,为什么说 m2 不是引用呢?返回类型不是 `T const &` 吗?
|
14
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 |