
代码来自 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 就算返回了临时变量的引用,但是也不意味着临时变量引用对应的地址会挪作他用,一般来说紧接着就用了而未进行压栈也大概率展示用不到 |
10 phiysng 2022-04-04 23:13:40 +08:00 @chuanqirenwu 未定义就是真的未定义,不存在`定义成了最接近预期结果的 behavior ` |
11 FrankHB &bsp; 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 |