墓碑挂在 std::future 的析构函数,提示空指针异常,这个 bug 好久了求大佬帮忙看看 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX    C++

墓碑挂在 std::future 的析构函数,提示空指针异常,这个 bug 好久了求大佬帮忙看看

  •  
  •   amiwrong123 2020-10-21 16:05:27 +08:00 2619 次点击
    这是一个创建于 1879 天前的主题,其中的信息可能已经有所发展或是发生改变。

    墓碑内容是:

    signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x10 Cause: null pointer dereference backtrace: #0 pc 0000000000069568 /system/lib64/vndk-sp-28/libc++.so (std::__1::future<void>::~future()+40) #01 pc 000000000000a6ec /vendor/lib64/xxx.so(我自己的 so) (android::BatchingConsumer<std::__1::shared_future<void>>::runInternal(std::__1::function<void (std::__1::vector<std::__1::shared_future<void>, std::__1::allocator<std::__1::shared_future<void>>> const&)> const&)+276) #02 pc 000000000000ab4c /vendor/lib64/xxx.so (_ZNSt3__114__thread_proxyINS_5tupleIJNS_10unique_ptrINS_15__thread_structENS_14default_deleteIS3_EEEEMN7android16BatchingConsumerINS_13shared_futureIvEEEEFvRKNS_8functionIFvRKNS_6vectorISA_NS_9allocatorISA_EEEEEEEEPSB_SK_EEEEEPvSR_+68) #03 pc 0000000000082fec /system/lib64/libc.so (__pthread_start(void*)+36) #04 pc 000000000002337c /system/lib64/libc.so (__start_thread+68) 

    但是看了半天,总觉得是不可能挂在析构函数的啊。而且这个问题也不是必现的,大部分时候机能都能正常工作。但一旦发生了这个墓碑,机器就会重启,就很严重。

    关于 future 的使用过程也很简单:

     std::shared_future<void> _future = std::async(std::launch::deferred, [=]() -> void { //balabala }); 

    就是使用一个 future 来包装一个 lambda 表达式(这里没有使用到 future 的异步功能,因为是 deferred ),然后将这个 future 放到一个消息队列里面去,然后与这个消息队列相关的有一个唯一消费者线程会被唤醒,然后这个线程将这个 future 对象出队,执行_future.wait(),因为消费者的 run 方法就是一个死循环不断出队 task 对象(也就是我们的 future 对象),所以在死循环的最后的大括号那里,会自动执行这个 future 对象的析构函数。

    上面的过程大多数时候都好使,但有的时候就会出现墓碑,这种问题到底可能是什么造成的啊?如果对象为 null,那么在执行_future.wait()的时候就应该报错啊,但现在却是在析构函数的时候报错。

    看了网上相关内容,可能也就这个有关系:

    https://zhuanlan.zhihu.com/p/39757902 提到的 std::async 会抓走所有异常。 https://blog.csdn.net/weixin_34256074/article/details/89412245 的匿名 std::thread 对象(感觉这个也和我这个没有关系)。

    刚又想到一点,难道跟 lambda 的参数捕获有关系吗?我用的复制[=]()

    7 条回复    2020-10-27 22:41:34 +08:00
    lonewolfakela
        1
    lonewolfakela  
       2020-10-21 17:30:05 +08:00
    虽然不知道是为啥,但是有一点很有趣的就是,你这个报错的好像是 std::future 的析构函数而不是 std::shared_future 的析构函数。似乎 std::async 返回的 std::future 在已经被 moved 的情况下,析构的时候却爆炸了。
    你这个场景里有什么必须使用 std::shared_future 的必要么?能不能直接用 std::future ?说不定避开“析构已经 moved 的 std::future”就能绕过这个问题?
    wutiantong
        2
    wutiantong  
       2020-10-21 18:05:59 +08:00
    我觉得“null pointer dereference”是一个指向性蛮强的错误类型(相比于,比如:释放野指针),有希望从代码里抠出问题来。
    amiwrong123
        3
    amiwrong123  
    OP
       2020-10-21 21:39:52 +08:00
    @lonewolfakela
    老哥,你说的话提醒了我,我能问你个问题吗,就是我这段代码,是 std::async 会返回一个 std::future 对象,然后赋值给一个 std::shared_future 对象,所以我这个 std::shared_future 对象持有一个 std::future 对象,我这么理解没错把。

    但如果我把这个 std::shared_future 对象使用等于=符号赋值给另一个新的 std::shared_future 对象的话(因为我发现我程序里确实会发生这种事。。),是不是发生的浅拷贝,就是说,这两个 std::shared_future 对象持有的 std::future 对象是同一个,所以这两个 std::shared_future 对象析构时,会析构同一个 std::future 对象两次,所以就会有墓碑。我这么分析对吗?

    但是我试验如下代码:
    ```
    int main()
    {
    {
    std::shared_future<void> _future = std::async(std::launch::deferred,
    [=]() -> void {
    //balabala
    int a = 1;
    });

    std::shared_future<void> _future2 = _future;
    }//到这个大括号会析构的
    }
    ```
    发现 VS 里面并没有报异常,哎,奇怪了
    lonewolfakela
        4
    lonewolfakela  
       2020-10-22 09:31:36 +08:00   1
    @amiwrong123 “我这么理解没错把。” 不完全正确。async 函数会返回一个 future 类型的右值,然后这个右值触发了 shared_future 的一个构造函数 [shared_future( future<T>&& other ) noexcept] ;此时 future 的内部状态会被移动到 shared_future 里如果你对 c++的“移动”这一概念不太熟悉的话,你可以简单理解为 future 的某些成员变量被先拷贝到 shared_future 里面,然后 future 里这些已经被拷贝过的变量就全部清空为 null 了。所以正常情况下,这个被移动过的 future 在析构的时候会检查自己的成员变量,发现是 null,就不会做 delete 之类的操作。
    将一个 shared_future 拷贝给另一个 shared_future 的话,默认执行的则不是移动而是拷贝构造 /赋值函数。shared_future 内的“状态”(通常是一个指针)确实会发生浅拷贝,但是在浅拷贝的同时还会有一个引用计数+1 的操作; shared_future 在析构的时候是会检查引用计数的,所以并不应该发生错误 delete 两次的问题。
    事实上 future 和 shared_future 之间的关系有点类似于智能指针里的 unique_ptr 和 shared_ptr,你可以类比着理解一下。

    另外我又仔细想了想你这个应用场景,感觉你完全用不着用 async 和 future 这套东西啊。你的消息队列完全只需要是一个 std::vector<std::function<void()>>就可以了。你这里用 async 只会增加 debug 难度……
    amiwrong123
        5
    amiwrong123  
    OP
       2020-10-22 10:30:05 +08:00
    @lonewolfakela
    好吧,我这一猜想看来又失败了。现在这件事分为两件事:
    1. 调查 bug 出现的原因,所以我才分析这么一大堆。我再看看这代码,分析分析原因吧
    2. 给出解决方案,你说的直接使用 std::vector<std::function<void()>>(其实我也觉得是这样,完全没有必要使用这些东西,但代码开始不是我写的,但这个 bug 让我来调查,蛋疼啊),我也正在试,希望好使吧。

    总之,谢谢老哥的认真回答啦
    Wirbelwind
        6
    Wirbelwind  
       2020-10-22 11:59:35 +08:00
    @amiwrong123 Cause: null pointer dereference 指的是*nullptr,这样的报错。

    优先检查*和其他会析构指针的地方比较好
    liam0x801
        7
    liam0x801  
       2020-10-27 22:41:34 +08:00
    首先非常不建议用来[=]捕获变量,这样会导致代码的可读性变差,比较推荐的方法是用到什么就把对应的变量声明出来。

    然后排查的点我认为也应该从传值的对象开始检查,要确保这些对象都是“拷贝安全”的。比如说可能引起你这个问题的一个场景是:你自己写了一个类或者用了一个别人的类,然后用 lamda 捕获,但是在传值的过程中某个对象发生了浅拷贝,最后在析构过程中对同一资源处理了两遍。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2627 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 14:51 PVG 22:51 LAX 06:51 JFK 09:51
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86