返回std ::领带 - 悬挂参考?

发布于 2025-02-08 03:49:55 字数 670 浏览 1 评论 0原文

关于从功能返回STD :: TIE的问题。如果我正确理解,那么STD :: TIE只包含参考。因此,返回一个std ::指向功能 - 本地变量的tie是一个非常糟糕的主意。编译器不应该检测到这一点并发出警告吗?

实际上,我们在代码中遇到了此错误,并且所有的编译器和消毒器都没有检测到它。令我感到困惑的是,任何工具都没有报告。还是我不正确地理解了什么?

#include <tuple>

struct s_t {
    int a;
};

int& foo(s_t s) {
    return s.a; // warning: reference to local variable 's' returned
}

int& bar(s_t &s) {
    return s.a; // ok
}

auto bad(s_t s) {
    return std::tie(s.a); // no warning
}

auto fine(s_t &s) {
    return std::tie(s.a); // no warning
}

int main() {

    s_t s1,s2;

    auto bad_references = bad(s1);
    auto good_references = fine(s2);
    // ...

    return 0;
}

Question on returning std::tie from a function. If I understand correctly, then std::tie only contains references. So, returning a std::tie which points to function-local variables is a very bad idea. Shouldn't the compiler be able to detect this and issue a warning ?

Actually, we had this error in our code and all compilers and sanitizers we have missed to detect it. I was quite puzzled that this didn't get reported by any tool. Or do I understand anything incorrectly?

#include <tuple>

struct s_t {
    int a;
};

int& foo(s_t s) {
    return s.a; // warning: reference to local variable 's' returned
}

int& bar(s_t &s) {
    return s.a; // ok
}

auto bad(s_t s) {
    return std::tie(s.a); // no warning
}

auto fine(s_t &s) {
    return std::tie(s.a); // no warning
}

int main() {

    s_t s1,s2;

    auto bad_references = bad(s1);
    auto good_references = fine(s2);
    // ...

    return 0;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

梦亿 2025-02-15 03:49:55

您对终身行为的理解是正确的。 std :: tie仅存储引用,并且您必须确保在引用对象被破坏后不使用它们。在函数调用的末尾或包含功能调用的全表达末尾(实现定义)在函数调用末尾或在全表达结束时被销毁。因此,使用存储在bad_references中的引用将导致不确定的行为。

您可能只会从编译器警告功能和Linter功能中期望过多。他们通常不会对代码进行广泛的分析。这里的分析需要通过多个功能调用层,成员的商店以及功能的返回来跟踪参考。如此复杂的分析是静态分析仪的目的。

但是,从版本12.1 GCC开始似乎使用了插入函数调用的结果来报告使用-O2 -WALL -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 = {int&}]' 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 = {int}]' 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,只要所有相关函数呼叫都被归类。使用EG -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); // no warning
    ^
<source>:27:27: note: Calling 'bad'
    auto bad_references = bad(s1);

请参见 https://godbolt.org.org/z/ze/ze8px8vt9

有叮当的中继和优化禁用的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
    #0 0x55678be240c3 in main /app/example.cpp:31:12
    #1 0x7fddb849e0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x240b2) (BuildId: 9fdb74e7b217d06c93172a8243f8547f947ee6d1)
    #2 0x55678bd6231d in _start (/app/output.s+0x2131d)

Address 0x7fddb5e00020 is located in stack of thread T0 at offset 32 in frame
    #0 0x55678be23d2f in bad(s_t) /app/example.cpp:15

  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/eo7cweam 。

可能的内部可能会使消毒剂更难检测到这一点。他们可能不会在内联之前添加支票。

Your understanding of the lifetime behavior is correct. std::tie stores only references and you must assure that they are not used after the referenced object is destroyed. By-value function parameters are destroyed either at the end of the function call or at the end of the full-expression containing the function call (implementation-defined). So using the references stored in bad_references will cause undefined behavior.

You might just be expecting too much from the compiler warning features and linter features. They generally don't do extensive analysis of the code. The analysis here would need to keep track of the reference through multiple function call layers, a store to a member and the return from the function. Such more complex analysis is what a static analyzer is for.

However, starting with version 12.1 GCC seems to use the result of inlining function calls to report with -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 = {int&}]' 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 = {int}]' 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

I didn't manage to get current Clang and MSVC to produce a diagnostic, though. I guess this will work for GCC also only as long as all the relevant function calls are inlined. With e.g. -O0 the GCC warning is not produced and it probably won't be produced either if there are more complex function call layers inbetween.

A static analyzer like clang-analyzer reports

<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); // no warning
    ^
<source>:27:27: note: Calling 'bad'
    auto bad_references = bad(s1);

See https://godbolt.org/z/zE8Px8vT9 for both.

With Clang trunk and optimizations disabled ASAN also reports the problem:

=================================================================
==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
    #0 0x55678be240c3 in main /app/example.cpp:31:12
    #1 0x7fddb849e0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x240b2) (BuildId: 9fdb74e7b217d06c93172a8243f8547f947ee6d1)
    #2 0x55678bd6231d in _start (/app/output.s+0x2131d)

Address 0x7fddb5e00020 is located in stack of thread T0 at offset 32 in frame
    #0 0x55678be23d2f in bad(s_t) /app/example.cpp:15

  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

[...]

See https://godbolt.org/z/eYo7cWeaM.

Probably the inlining makes it harder for the sanitizers to detect this. They probably don't add the checks prior to inlining.

南城旧梦 2025-02-15 03:49:55

您应该知道自我提出的问题是RAII(资源获取是初始化 - 有人说这是C ++中最重要的事情)
在您的示例中:

foo(s_t s);将通过复制构造器初始化s。当foo退出时,s将被销毁,以便sa引用悬挂对象

bar(s_t&amp; s)是可以的&amp; s 是参考存在的变量(通常)以上函数调用

bad(s_t s) fine(s_t&amp; s)是可以的是返回值而不是引用/指针到本地变量

What you should know to self-answer this question is RAII (resource acquisition is initialization- Some one say it is the most importance thing needed to know in C++)
In your example:

foo(s_t s); will initialize s via copy constructor. When foo exit, s will be destroyed so that s.a referencing to a dangling object

bar(s_t &s) is ok because &s is reference to existed variable (that usually) outlive the function call

bad(s_t s) fine(s_t &s) is ok because it is return value not reference/pointer to local variable

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文