std::bind and stack-use-after-scope
std::bind and stack-use-after-scope
所以,今天,我正在运行一些使用地址消毒剂构建的代码,并偶然发现了一个奇怪的堆栈使用后的错误。我有一个简化的例子:
#include <functional>
class k
{
public: operator int(){return 5;}
};
const int& n(const int& a)
{
return a;
}
int main()
{
k l;
return std::bind(n, l)();
}
Asan抱怨最后一个代码行:
==27575==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffeab375210 at pc 0x000000400a01 bp 0x7ffeab3750e0 sp 0x7ffeab3750d8
READ of size 4 at 0x7ffeab375210 thread T0
#0 0x400a00 (/root/tstb.exe+0x400a00)
#1 0x7f97ce699730 in __libc_start_main (/lib64/libc.so.6+0x20730)
#2 0x400a99 (/root/tstb.exe+0x400a99)
Address 0x7ffeab375210 is located in stack of thread T0 at offset 288 in frame
#0 0x40080f (/root/tstb.exe+0x40080f)
This frame has 6 object(s):
[32, 33) '<unknown>'
[96, 97) '<unknown>'
[160, 161) '<unknown>'
[224, 225) '<unknown>'
[288, 292) '<unknown>' <== Memory access at offset 288 is inside this variable
[352, 368) '<unknown>'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope (/root/tstb.exe+0x400a00)
Shadow bytes around the buggy address:
0x1000556669f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055666a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055666a10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1
0x100055666a20: f1 f1 f8 f2 f2 f2 f2 f2 f2 f2 f8 f2 f2 f2 f2 f2
0x100055666a30: f2 f2 f8 f2 f2 f2 f2 f2 f2 f2 f8 f2 f2 f2 f2 f2
=>0x100055666a40: f2 f2[f8]f2 f2 f2 f2 f2 f2 f2 00 00 f2 f2 f3 f3
0x100055666a50: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055666a60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055666a70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055666a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100055666a90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==27575==ABORTING
如果我正确理解,它说我们正在访问堆栈变量后,它已经消失了范围。查看未爆炸且不优化的拆卸,我确实看到它发生在实例化的__invoke_impl
中:
Dump of assembler code for function std::__invoke_impl<int const&, int const& (*&)(int const&), k&>(std::__invoke_other, int const& (*&)(int const&), k&):
0x0000000000400847 <+0>: push %rbp
0x0000000000400848 <+1>: mov %rsp,%rbp
0x000000000040084b <+4>: push %rbx
0x000000000040084c <+5>: sub $0x28,%rsp
0x0000000000400850 <+9>: mov %rdi,-0x28(%rbp)
0x0000000000400854 <+13>: mov %rsi,-0x30(%rbp)
0x0000000000400858 <+17>: mov -0x28(%rbp),%rax
0x000000000040085c <+21>: mov %rax,%rdi
0x000000000040085f <+24>: callq 0x4007a2 <std::forward<int const& (*&)(int const&)>(std::remove_reference<int const& (*&)(int const&)>::type&)>
0x0000000000400864 <+29>: mov (%rax),%rbx
0x0000000000400867 <+32>: mov -0x30(%rbp),%rax
0x000000000040086b <+36>: mov %rax,%rdi
0x000000000040086e <+39>: callq 0x4005c4 <std::forward<k&>(std::remove_reference<k&>::type&)>
0x0000000000400873 <+44>: mov %rax,%rdi
0x0000000000400876 <+47>: callq 0x40056a <k::operator int()>
0x000000000040087b <+52>: mov %eax,-0x14(%rbp)
0x000000000040087e <+55>: lea -0x14(%rbp),%rax
0x0000000000400882 <+59>: mov %rax,%rdi
0x0000000000400885 <+62>: callq *%rbx
=> 0x0000000000400887 <+64>: add $0x28,%rsp
0x000000000040088b <+68>: pop %rbx
0x000000000040088c <+69>: pop %rbp
0x000000000040088d <+70>: retq
End of assembler dump.
调用k::operator int()
后,它将返回的值放在堆栈上并将其地址传递给n()
,该值立即返回它,然后从__invoke_impl
本身返回(并一直返回到Main的返回(。
所以,它看起来像是Asan在这里,我们确实有一个堆栈使用后的访问。
问题是:我的代码怎么了?
我尝试使用GCC,Clang和ICC构建它,它们都产生相似的汇编输出。
std::bind
基本上生成了一个用所需参数调用绑定函数的实现函数对象。在您的情况下,此实现函数对象大约等于
struct Impl
{
const int &operator()() const
{
int tmp = k_;
return n(tmp);
}
private:
k k_;
Impl(/*unspecified*/);
};
由于n
将其参数返回作为const引用,因此Impl
的呼叫操作员将返回对本地变量的引用,该变量是悬挂的参考,然后在main
中读取。因此,示波器错误后的堆栈使用。
您的混乱可能源于没有bind
的return n(l);
在这里工作正常。但是,在后一种情况下,临时int
是在main
的堆栈框架中创建的,该列为完整表达式的持续时间,构成了return
的参数,该参数已评估为int
。
换句话说,虽然暂时生存直到创建其创建的完整表达结束,但对于在该完整表达式中调用的函数中生成的临时性并非如此。这些被认为是a 不同的全部表达的一部分,并在评估该表达时被破坏。
ps:因此,将签名R(Args...)
的任何功能(对象(绑定到std::function<const R&(Args...)>
的任何函数(对象(会导致悬挂参考的保证返回时,称为IMO库在编译时库应拒绝。
好吧,如果您不知道std::bind
的细节。
使用std::bind
将参数绑定到可呼叫时,参数的A copy 是女仆(来源(:
绑定的论点是复制或移动的,除非包裹在std :: ref或std :: cref。
std::bind(n, l)
返回一个可呼叫的对象的未指定类型的可可对象,该对象具有k
类型的成员对象为l
的副本。请注意,此可呼叫对象是一个临时的( rvalue (,我给它一个名称: bindtmp 。
调用后,bindtmp()
会创建一个临时( inttemp (整数(5(以将bindtmp::lcopy
应用于bindtmp::ncopy
(这些是由main::l
和::n
构建的成员对象(。::n
在返回语句中的bindtmp()
范围内返回inttemp
的const引用。
这是事物变得棘手的地方(来源(:
每当参考与临时或子对象绑定时,临时的寿命会延长以匹配参考的寿命,但以下例外:
-A 临时绑定到返回语句中函数的返回值的临时绑定:在返回表达式结束时,它立即被销毁。这样的功能总是返回悬而未决的参考。
- ...
这意味着,::n
返回后临时inttemp
被销毁。
从这一点开始,一切都崩溃了。bindtmp()
返回对其寿命已经结束的对象的引用,main
尝试并将其转换为 lvalue ,在不确定的行为(使用后从堆栈中使用ODR使用(发生的对象。
- 如何处理 c++ 中类实现中的"invalid use of non-static data member"?
- C++ "error: invalid use of void expression"
- 我看到"use of undeclared identifier"错误,有人可以告诉我如何解决它吗?
- 收到错误"invalid use of non-static data member 'stu::n' "
- 模式"allocate memory or use existing data"
- std::stack 是连续的吗?
- 为什么我会收到"Run-Time Check Failure #2 - Stack around the variable 'pr' was corrupted"错误?
- C++ "error: use of overloaded operator '*' is ambiguous"似乎只有一场比赛
- Is it good to use SDL_PIXELFORMAT_UNKNOWN?
- Incomings Call with Android Sip stack in Embarcadero C++ bui
- C++ "Invalid use of 'this' in non-member function" ,
- 堆叠协程 + gdb = "previous frame inner to this frame (corrupt stack)?"
- std::launder use cases in C++20
- 继承类时"invalid use of incomplete type ‘class tree_node_t’"
- "reserved for any use"的含义是什么?
- 生成质数的程序,错误:"Stack overflow"
- C++14 遇到奇怪的"use of deleted function"错误
- std::bind and stack-use-after-scope
- "used after it was moved [bugprone-use-after-move]"警告在这里是一个真正的问题吗?
- 我正在得到掩护性 issuse 作为"Wrapper object use after free (WRAPPER_ESCAPE)"