最近,我在一个程序中捕获lambda中的shared_ptr
时,遇到了一个奇怪的双重免费bug。我可以用以下最小的例子来减少它:
#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;
}
使用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
一旦get
函数返回,foo
结构中的std::function
拥有唯一拥有封闭的foo
对象的shared_ptr
。这意味着,将nullptr
分配给它应该会破坏shared_ptr
,这反过来会释放foo
对象。
这里发生的似乎是std_function中的
首先运行lambda的析构函数,它在释放内存之前销毁delete
调用。h: 175shared_ptr
、foo
object及其附带的
std::function对象现在已经释放了该内存位置,从而导致双空闲。
我现在试图弄清楚这是标准库实现(libstdc
)中的一个bug,还是程序在某处触发了未定义的行为。
这可能是< code>libstdc 错误的一个标志是,在< code>clang 和< code>libc 14.0.6中,没有双释放(或者至少没有检测到),但是< code > clang with < code > libstdc 也有双释放问题。
这个程序是否违反了任何规则/根据任何C标准触发未定义的行为?
我在x86-64 linux机器上复制了所有这些。
我认为该标准的相关部分是[物体上的物体],其中规定
如果访问了标准库类型的对象,并且对象生存期的开始在访问之前不会发生,或者访问未在对象的生存期结束之前发生,则除非另有指定,否则行为未定义。
在您的示例中,您可以通过分配给 std:: 函数
来访问它。在此访问期间,将调用 std::函数
的析构函数,结束 std::函数
的生存期。但是访问尚未完成,因此访问不会在对象的生存期结束之前发生。
因此,代码具有未定义的行为。
标准库函数bind()和function()定义于头文件<functional>中(该头文件还包括许多其他函数对象),用于处理函数及函数参数。bind()接受一个函数(或者函数对象,或者任何你可以通过”(…)”符号调用的事物),生成一个其有某一个或多个函数参数被“绑定”或重新组织的函数对象。(译注:顾名思义,bind()函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。)例如: int
在设计回调函数的时候,无可避免地会接触到可回调对象。在C++11中,提供了std::function和std::bind两个方法来对可回调对象进行统一和封装。 可调用对象 C++中有如下几种可调用对象:函数、函数指针、lambda表达式、bind对象、函数对象。其中,lambda表达式和bind对象是C++11标准中提出的(bind机制并不是新标准中首次提出,而是对旧版本中bind1st和bind
所有报价均来自N3797。 4/3[conv] 表达式e可以隐式转换为类型T,当且仅当声明T=e;对于一些发明的临时变量t来说 这意味着任何表达式都不能隐式转换为,因为对于所有表达式都是非法的。即使是类型的表达式,例如,也是如此。 因此,类型的表达式不能隐式转换为。 这让我们想到: 20.9.2/2要求[func.require] 将INVOKE(f, t1, t2,..., tN, R)定义为隐
在标准库中,tuple(一个N元组:N-tuple)被定义为N个值的有序序列。在这里,N可以是从0到文件中所定义的最大值中的任何一个常数。你可以认为tuple是一个未命名的结构体,该结构体包含了特定的tuple元素类型的数据成员。特别需要指出的是,tuple中元素是被紧密地存储的(位于连续的内存区域),而不是链式结构。 可以显式地声明tuple的元素类型,也可以通过make_tuple()来推断出
我在理解条件变量及其在互斥体中的使用时遇到了一些困难,我希望社区能帮助我。请注意,我来自win32背景,因此与CRITICAL_SECTION、HANDLE、SetEvent、WaitForMultipleObject等一起使用。 这是我第一次尝试使用C++11标准库进行并发操作,它是在这里找到的一个程序示例的修改版本。 关于这个的几个问题。 我读过“任何要等待std::condition_var
在我的Fedora 34环境(g)中,定义为: 如果表达式已经是右值,那么