Pat*_*ler 32 c++ g++ libstdc++ undefined-behavior compiler-bug
shared_ptr我最近在 lambda 中捕获 a 时在程序中遇到了一个奇怪的双重释放错误。我能够将其减少为以下最小示例:
#include <memory>
#include <functional>
struct foo {
std::function<void(void)> fun;
};
foo& get() {
auto f = std::make_shared<foo>();
// Create a circular reference by capturing the shared pointer by value
f->fun = [f]() {};
return *f;
}
int main(void) {
get().fun = nullptr;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
使用 GCC 12.2.0 和地址清理程序编译并运行它,会在 中产生双重释放std::function:
$ g++ -fsanitize=address -g -Wall -Wextra -o main main.cpp && ./main
=================================================================
==2401674==ERROR: AddressSanitizer: attempting double-free on 0x602000000010 in thread T0:
#0 0x7f7064ac178a in operator delete(void*, unsigned long) /usr/src/debug/gcc/libsanitizer/asan/asan_new_delete.cpp:164
#1 0x556a00865b9d in _M_destroy /usr/include/c++/12.2.0/bits/std_function.h:175
#2 0x556a00865abe in _M_manager /usr/include/c++/12.2.0/bits/std_function.h:203
#3 0x556a008658b9 in _M_manager /usr/include/c++/12.2.0/bits/std_function.h:282
#4 0x556a00866623 in std::function<void ()>::operator=(decltype(nullptr)) /usr/include/c++/12.2.0/bits/std_function.h:505
#5 0x556a008654b5 in main /tmp/cpp/main.cpp:16
#6 0x7f706443c28f (/usr/lib/libc.so.6+0x2328f)
#7 0x7f706443c349 in __libc_start_main (/usr/lib/libc.so.6+0x23349)
#8 0x556a008651b4 in _start ../sysdeps/x86_64/start.S:115
0x602000000010 is located 0 bytes inside of 16-byte region [0x602000000010,0x602000000020)
freed by thread T0 here:
#0 0x7f7064ac178a in operator delete(void*, unsigned long) /usr/src/debug/gcc/libsanitizer/asan/asan_new_delete.cpp:164
#1 0x556a00865b9d in _M_destroy /usr/include/c++/12.2.0/bits/std_function.h:175
#2 0x556a00865abe in _M_manager /usr/include/c++/12.2.0/bits/std_function.h:203
#3 0x556a008658b9 in _M_manager /usr/include/c++/12.2.0/bits/std_function.h:282
#4 0x556a00866215 in std::_Function_base::~_Function_base() /usr/include/c++/12.2.0/bits/std_function.h:244
#5 0x556a00866579 in std::function<void ()>::~function() /usr/include/c++/12.2.0/bits/std_function.h:334
#6 0x556a00868337 in foo::~foo() /tmp/cpp/main.cpp:4
#7 0x556a00868352 in void std::_Destroy<foo>(foo*) /usr/include/c++/12.2.0/bits/stl_construct.h:151
#8 0x556a0086830d in void std::allocator_traits<std::allocator<void> >::destroy<foo>(std::allocator<void>&, foo*) /usr/include/c++/12.2.0/bits/alloc_traits.h:648
#9 0x556a008680fa in std::_Sp_counted_ptr_inplace<foo, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() /usr/include/c++/12.2.0/bits/shared_ptr_base.h:613
#10 0x556a00866005 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/include/c++/12.2.0/bits/shared_ptr_base.h:346
#11 0x556a008664c5 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/12.2.0/bits/shared_ptr_base.h:1071
#12 0x556a00866235 in std::__shared_ptr<foo, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/12.2.0/bits/shared_ptr_base.h:1524
#13 0x556a00866251 in std::shared_ptr<foo>::~shared_ptr() /usr/include/c++/12.2.0/bits/shared_ptr.h:175
#14 0x556a008652ad in ~<lambda> /tmp/cpp/main.cpp:10
#15 0x556a00865b90 in _M_destroy /usr/include/c++/12.2.0/bits/std_function.h:175
#16 0x556a00865abe in _M_manager /usr/include/c++/12.2.0/bits/std_function.h:203
#17 0x556a008658b9 in _M_manager /usr/include/c++/12.2.0/bits/std_function.h:282
#18 0x556a00866623 in std::function<void ()>::operator=(decltype(nullptr)) /usr/include/c++/12.2.0/bits/std_function.h:505
#19 0x556a008654b5 in main /tmp/cpp/main.cpp:16
#20 0x7f706443c28f (/usr/lib/libc.so.6+0x2328f)
previously allocated by thread T0 here:
#0 0x7f7064ac0672 in operator new(unsigned long) /usr/src/debug/gcc/libsanitizer/asan/asan_new_delete.cpp:95
#1 0x556a00865906 in _M_create<get()::<lambda()> > /usr/include/c++/12.2.0/bits/std_function.h:161
#2 0x556a008657e3 in _M_init_functor<get()::<lambda()> > /usr/include/c++/12.2.0/bits/std_function.h:215
#3 0x556a00865719 in function<get()::<lambda()> > /usr/include/c++/12.2.0/bits/std_function.h:449
#4 0x556a00865578 in operator=<get()::<lambda()> > /usr/include/c++/12.2.0/bits/std_function.h:534
#5 0x556a008653aa in get() /tmp/cpp/main.cpp:10
#6 0x556a008654a8 in main /tmp/cpp/main.cpp:16
#7 0x7f706443c28f (/usr/lib/libc.so.6+0x2328f)
SUMMARY: AddressSanitizer: double-free /usr/src/debug/gcc/libsanitizer/asan/asan_new_delete.cpp:164 in operator delete(void*, unsigned long)
==2401674==ABORTING
Run Code Online (Sandbox Code Playgroud)
一旦get函数返回,结构std::function内部foo就拥有唯一shared_ptr拥有封闭foo对象的结构。这意味着,分配nullptr给它应该会破坏 ,shared_ptr而反过来又应该释放该foo对象。
这里似乎发生的是,deletein 的调用std_function.h:175首先运行 lambda 的析构函数,该析构函数在释放内存之前销毁shared_ptr、foo对象及其封闭的std::function对象。然而,对象的销毁std::function现在已经释放了该内存位置,从而导致了双重释放。
我现在试图弄清楚这是否是标准库实现中的错误(libstdc++)或者程序是否在某处触发了未定义的行为。
这可能是一个libstdc++错误的一个迹象是,在clang++and libc++14.0.6 中,没有双重释放(或者至少没有检测到),但clang++在 with 中libstdc++也存在双重释放问题。
根据任何 C++ 标准,该程序是否违反任何规则/触发未定义的行为?
我在 x86-64 Linux 机器上重现了这一切。
Eri*_*idt 33
我相信标准的相关部分是[res.on.objects],其中规定
如果访问标准库类型的对象,并且该对象的生命周期的开始没有发生在该访问之前,或者该访问没有发生在该对象的生命周期结束之前,则除非另有指定,否则该行为是未定义的。
在您的示例中,您可以std::function通过分配来访问它。在此访问期间,调用 的析构函数std::function,结束 的生命周期std::function。但访问尚未完成,因此访问不会发生在对象生命周期结束之前。
因此,该代码具有未定义的行为。