您对生命周期行为的理解是正确的。std::tie
仅存储引用,您必须确保在所引用的对象被销毁后不再使用它们。按值传递的函数参数会在函数调用结束或包含函数调用的完整表达式结束时被销毁(实现定义)。因此,使用存储在 bad_references
中的引用将导致未定义的行为。
您可能只是期望编译器警告功能和代码检查工具提供的功能太多了。它们通常不会对代码进行全面的分析。这里的分析需要跟踪引用经过多个函数调用层、成员变量的存储和函数返回。这种更复杂的分析正是静态分析器的任务。
然而,从版本12.1开始,GCC似乎使用内联函数调用的结果来报告 -O2 -Wall -Wextra
选项:
In file included from <source>:1:
In constructor 'constexpr std::_Head_base<_Idx, _Head, false>::_Head_base(const _Head&) [with long unsigned int _Idx = 0; _Head = int&]',
inlined from 'constexpr std::_Tuple_impl<_Idx, _Head>::_Tuple_impl(const _Head&) [with long unsigned int _Idx = 0; _Head = int&]' at /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/tuple:435:21,
inlined from 'constexpr std::tuple< <template-parameter-1-1> >::tuple(const _Elements& ...) [with bool _NotEmpty = true; typename std::enable_if<_TCC<_Dummy>::__is_implicitly_constructible<const _Elements& ...>(), bool>::type <anonymous> = true; _Elements = ]' at /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/tuple:729:28,
inlined from 'constexpr std::tuple<_Elements& ...> std::tie(_Elements& ...) [with _Elements = ]' at /opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/tuple:1745:44,
inlined from 'auto bad(s_t)' at <source>:16:24:
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/tuple:193:9: warning: storing the address of local variable 's' in '*(std::_Head_base<0, int&, false>*)<return-value>.std::_Head_base<0, int&, false>::_M_head_impl' [-Wdangling-pointer=]
193 | : _M_head_impl(__h) { }
| ^~~~~~~~~~~~~~~~~
<source>: In function 'auto bad(s_t)':
<source>:15:14: note: 's' declared here
15 | auto bad(s_t s) {
| ~~~~^
<source>:15:14: note: '<unknown>' declared here
虽然我没有设法让当前版本的Clang和MSVC产生诊断信息,但我猜这对于GCC而言也只有在所有相关函数调用都被内联时才会起作用。例如,使用-O0
时,GCC不会产生警告,如果存在更多复杂的函数调用层,则可能也不会产生警告。
像clang-analyzer这样的静态分析器会报告
<source>:16:5: warning: Address of stack memory associated with local variable 's' is still referred to by the stack variable 'bad_references' upon returning to the caller. This will be a dangling reference [clang-analyzer-core.StackAddressEscape]
return std::tie(s.a)
^
<source>:27:27: note: Calling 'bad'
auto bad_references = bad(s1)
请参阅
https://godbolt.org/z/zE8Px8vT9,其中包含相关信息。
使用Clang trunk且未启用优化时,ASAN也会报告问题:
=================================================================
==1==ERROR: AddressSanitizer: stack-use-after-return on address 0x7fddb5e00020 at pc 0x55678be240c4 bp 0x7ffe4ed87ef0 sp 0x7ffe4ed87ee8
READ of size 4 at 0x7fddb5e00020 thread T0
Address 0x7fddb5e00020 is located in stack of thread T0 at offset 32 in frame
This frame has 1 object(s):
[32, 36) 's' <== Memory access at offset 32 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-return /app/example.cpp:31:12 in main
[...]
请查看https://godbolt.org/z/eYo7cWeaM。
可能是内联导致Sanitizer难以检测到此问题。他们可能不会在内联之前添加检查。