首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在C++标准库中只使用std::function和std::shared_pointer的双空闲

在C++标准库中只使用std::function和std::shared_pointer的双空闲
EN

Stack Overflow用户
提问于 2022-10-15 14:12:32
回答 1查看 1.3K关注 0票数 30

最近,当我在lambda中捕获shared_ptr时,我在一个程序中遇到了一个奇怪的、没有双重功能的bug。我能够把它减少到以下最小的例子:

代码语言:javascript
复制
#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和address杀菌剂编译它并运行它,在std::function中产生一个双自由的

代码语言:javascript
复制
$ 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对象。

这里似乎发生的事情是,delete调用在std_function.h:175中首先运行lambda的析构函数,该函数在释放内存之前破坏shared_ptrfoo对象及其封闭的std::function对象。然而,std::function对象的破坏现在已经释放了该内存位置,从而导致了双空闲。

我现在试图弄清楚这是标准库实现(libstdc++)中的一个bug,还是程序在某个地方触发了未定义的行为。

表明这可能是libstdc++缺陷的一个指标是,使用clang++libc++ 14.0.6时,不存在双重空闲(或至少没有检测到),但是带有libstdc++clang++也存在双空闲问题。

该程序是否违反了任何规则/根据任何C++标准触发了未定义的行为?

我在x86-64 linux机器上复制了所有这些内容。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-10-15 15:17:31

我相信标准的相关部分是[res.on.objects],它声明

如果访问标准库类型的对象,且对象的生存期开始于访问之前,或访问未在对象的生存期结束之前发生,则除非另有规定,否则行为将未定义。

在您的示例中,通过分配给std::function来访问它。在此访问期间,将调用std::function的析构函数,从而结束std::function的生存期。但是访问还没有完成,所以访问并不是在对象生命周期结束之前进行的。

因此,代码具有未定义的行为。

票数 32
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74080177

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档