求 c++大神帮忙看下这段代码为啥不崩 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
scinart
V2EX    C

求 c++大神帮忙看下这段代码为啥不崩

  •  2
     
  •   scinart 2017-06-27 17:03:22 +08:00 3850 次点击
    这是一个创建于 3036 天前的主题,其中的信息可能已经有所发展或是发生改变。
    #include <vector> class Holder { int* p; public: Holder(int x) { p=new int(); *p=x; } ~Holder() { delete p; } }; int main(int argc, char *argv[]) { std::vector<Holder> v; v.reserve(3); v.emplace_back(1); v.emplce_back(2); v.emplace_back(3); v[1].~Holder(); return 0; } 

    应该崩是不是,但是我在 linux 上死活不崩,cpp.sh 上也正常运行。去 stackoverflow 问之前先看看 v 友有没有能解答的?

    23 条回复    2017-06-28 22:32:45 +08:00
    bombless
        1
    bombless  
       2017-06-27 17:12:42 +08:00   1
    double free 是 UB 吧,UB 就是…… UB ……
    liuzhedash
        2
    liuzhedash  
       2017-06-27 17:19:09 +08:00
    是否崩是操作系统钦定的,当然代码的决定权也很重要
    xss
        3
    xss  
       2017-06-27 17:19:29 +08:00
    为啥要崩?并没有看到 double free.
    你的意思是, 你指望 Vector 在结束生命周期的时候自动调用里面元素的析构函数?
    wwqgtxx
        4
    wwqgtxx  
       2017-06-27 17:30:37 +08:00
    你可以再析构函数中输出看一看析构函数到底调用了几次
    scinart
        5
    scinart  
    OP
       2017-06-27 17:33:21 +08:00
    不小心给自己点了个赞,还不知道怎么撤销,也不知道怎么添加附言。

    结论已经有了,是 Undefined Behavior. 万万没想到。
    v2exchen
        6
    v2exchen  
       2017-06-27 17:49:26 +08:00
    测试了一下,通过 printf 打印调试,发现系统在执行 v[1].~Holder()的时候先调用了 Holder()。
    zts1993
        7
    zts1993  
       2017-06-27 18:07:14 +08:00
    @v2exchen #6 没有吧........///啥编译器?
    bp0
        8
    bp0  
       2017-06-27 18:43:57 +08:00 via Android
    考虑一下 vector 空的情况下直接用下标访问会发生什么事情
    stormpeach
        9
    stormpeach  
       2017-06-27 19:45:56 +08:00
    @xss vector 中内建有内存管理,当 vector 离开它的生存期的时候,它的析构函数会把其中的元素销毁,并释放它们所占用的空间,所以用一般不用显式释放。不过,如果你 vector 中存放的是指针,那么当销毁时,那些指针指向的对象不会被销毁,那些内存不会被释放。
    dayoushen
        10
    dayoushen  
       2017-06-27 20:53:53 +08:00
    我的测试工具是 VS2010,Centos 的 4.8.5,运行结果是 VS 会弹框报错,注释掉 v[1].~Holder();就 OK ;然而 Centos 中即使注释掉 v[1].~Holder()也会 double free 报错,因为 v.emplace_back(2);构造是隐式构造,即浅拷贝,需要再添加深拷贝才能正常运行:
    #include <iostream>
    #include <vector>
    using namespace std;
    class Holder
    {
    int* p;
    public:
    Holder(int x) {
    cout<<"Holder() : "<<x<<endl;
    p=new int();
    *p=x;
    }
    Holder(const Holder &aH)
    {
    cout<<"copy construct Holder()"<<endl;
    p= new int();
    *p=*(aH.p);
    }
    ~Holder() {
    cout<<"~Holder() : "<<*p<<endl;
    //if(p != NULL)
    delete p;
    }

    };
    int main(int argc, char *argv[])
    {
    vector<Holder> v;
    v.reserve(3);
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    return 0;
    }
    dayoushen
        11
    dayoushen  
       2017-06-27 21:02:03 +08:00
    @dayoushen 通过打印 VS2010 中的地址,发现 VS 的 vector 实现既然不做隐式转换,具体代码如下:
    class Holder
    {
    int* p;
    public:
    Holder(int x) {
    p=new int();
    cout<<"Holder() : "<<&p<<endl;
    *p=x;
    }
    Holder(const Holder &aH)
    {
    cout<<"copy construct Holder()"<<endl;
    p= new int();
    *p=*(aH.p);
    }
    ~Holder() {
    cout<<"~Holder() : "<<&p<<endl;
    delete p;
    }
    };
    int main(int argc, char *argv[])
    {
    std::vector<Holder> v;
    v.reserve(3);
    v.emplace_back(1);
    v.emplace_back(2);
    v.emplace_back(3);
    v[1].~Holder();
    return 0;
    }
    输出:
    Holder() : 00533928
    Holder() : 0053392C
    Holder() : 00533930
    ~Holder() : 0053392C
    ~Holder() : 00533928
    ~Holder() : 0053392C
    azh7138m
        12
    azh7138m  
       2017-06-27 22:05:15 +08:00 via Android
    @dayoushen 按道理是先构造一个临时的,再赋值过去,这样子嘛?
    gnaggnoyil
        13
    gnaggnoyil  
       2017-06-28 05:46:08 +08:00
    @dayoushen 所以兄弟你看清楚了没人家楼主用的是 emplace_back 而不是 push_back.我建议你先打开-std=c++11 再来说话吧.
    ptrvoid
        14
    ptrvoid  
       2017-06-28 09:13:02 +08:00
    xss
        15
    xss  
       2017-06-28 09:31:29 +08:00
    @stormpeach 然而, emplace_back 是用 placement new 分配内存的啊, 虽然不在堆上, 但也是指针啊.
    所以:
    ```
    ~vector();
    Destructs the container. The destructors of the elements are called and the used storage is deallocated. Note, that if the elements are pointers, the pointed-to objects are not destroyed.
    ```
    enenaaa
        16
    enenaaa  
       2017-06-28 11:12:32 +08:00
    @xss 这题跟 emplace_back 没什么关系吧。不用 push_back 是为了省掉拷贝构造函数。
    v[1].~Holder(); 手动调用析构函数,指针释放了一次。
    main 退出时, ~vector 又会调用 Holder 析构函数。造成指针被多次释放。
    这是个未定义行为,我用 vc2015 测试, 是会报异常的。
    xss
        17
    xss  
       2017-06-28 12:40:12 +08:00
    @enenaaa
    [这里]( https://stackoverflow.com/questions/14187006/is-calling-destructor-manually-always-a-sign-of-bad-design)
    有一个讨论, 是和 placement new 相关的.

    现在比较明确的是, 这个问题应该是'未定义行为'的锅.

    >> cat a.cxx
    #include <vector>
    #include <iostream>

    int* g_p = NULL;
    class Holder
    {
    int* p;
    public:
    Holder(int x) {
    p=new int();
    *p=x;
    if(x == 3) {
    g_p = p;
    }
    }
    ~Holder() {
    std::cout<< "[destruct] pointer:" << p << " value:" << *p << std::endl;
    delete p;
    }
    };
    void foo(){
    std::vector<Holder> v;
    v.reserve(3);
    v.emplace_back(1);
    v.emplace_back(2);
    v.emplace_back(3);
    std::cout << "manual destruct start" << std::endl;
    v[1].~Holder();
    std::cout << "manual destruct end" << std::endl;
    std::cout << "befor destruct" << std::endl;
    std::cout<< "pointer:" << g_p << " value:" << *g_p << std::endl;
    std::cout << "befor destruct end" << std::endl;
    }
    int main(int argc, char *argv[])
    {
    foo();
    std::cout << "[out foo] pointer:" << g_p << " value:" << *g_p << std::endl;
    return 0;
    }
    >> g++ a.cxx -o a

    >> ./a
    manual destruct start
    [destruct] pointer:0x771c60 value:2
    manual destruct end
    befor destruct
    pointer:0x771c80 value:3
    befor destruct end
    [destruct] pointer:0x771c40 value:1
    [destruct] pointer:0x771c60 value:0
    [destruct] pointer:0x771c80 value:3
    [out foo] pointer:0x771c80 value:7806032

    linux & g++下的结果, 比较有意思的是, 在 manually call dtor 之后, 0x771c60 的值是 0(多次运行, 值都是 0), 所以, 内存依然是有效的(?), 如果上述推论成立, 那么再次 delete 自然不会 double free.
    gnaggnoyil
        18
    gnaggnoyil  
       2017-06-28 13:27:30 +08:00
    @xss 所以这里哪里有作为 vector 的 element 的(裸)指针出现?
    wangjxxx
        19
    wangjxxx  
       2017-06-28 13:55:49 +08:00
    1 楼正解,double free 没有说一定崩溃吧
    dayoushen
        20
    dayoushen  
       2017-06-28 13:58:47 +08:00
    @xss 因为 centos 的 g++没有 emplace_back,只有 push_back,所以改了一下,发现是看错了。这段代码鲁棒性太差,首先构造函数中 p 应先初始化为 NULL 再用 new int 复制, 析构中则是 if(p!=NULL) { delete p; p = NULL;}这样无论使用者怎么析构都不会面临奔溃的情况,不然生或死由编译器决定。
    seancheer
        21
    seancheer  
       2017-06-28 14:08:34 +08:00
    @dayoushen emplace_back 是 c++ 11 的,和系统无关。。
    xss
        22
    xss  
       2017-06-28 14:32:58 +08:00
    @gnaggnoyil 我理解错文档里面的意思了.
    http://en.cppreference.com/w/cpp/container/vector/emplace_back
    这个里面的 placemant-new 应该是'as is'的意思, 对象在哪里构造, vector 就放在那里构造的对象....
    shibingsw
        23
    shibingsw  
       2017-06-28 22:32:45 +08:00
    代码肯定有问题,double free 了,但是因为 double free 是未定义的,所以不一定会挂掉。
    用 valgrind 也能清楚的看到多了一次 free:
    ```
    ==5561== Invalid free() / delete / delete[] / realloc()
    ==5561== at 0x4C2C64B: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==5561== by 0x4009C7: Holder::~Holder() (in /vagrant/Work/waimai/inf_waf/j/build/a.out)
    ==5561== by 0x40161A: void std::_Destroy<Holder>(Holder*) (in /vagrant/Work/waimai/inf_waf/j/build/a.out)
    ==5561== by 0x401500: void std::_Destroy_aux<false>::__destroy<Holder*>(Holder*, Holder*) (in /vagrant/Work/waimai/inf_waf/j/build/a.out)
    ==5561== by 0x401187: void std::_Destroy<Holder*>(Holder*, Holder*) (in /vagrant/Work/waimai/inf_waf/j/build/a.out)
    ==5561== by 0x400CE4: void std::_Destroy<Holder*, Holder>(Holder*, Holder*, std::allocator<Holder>&) (in /vagrant/Work/waimai/inf_waf/j/build/a.out)
    ==5561== by 0x400A1C: std::vector<Holder, std::allocator<Holder> >::~vector() (in /vagrant/Work/waimai/inf_waf/j/build/a.out)
    ==5561== by 0x40092B: main (in /vagrant/Work/waimai/inf_waf/j/build/a.out)
    ==5561== Address 0x5a9fd30 is 0 bytes inside a block of size 4 free'd
    ==5561== at 0x4C2C64B: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==5561== by 0x4009C7: Holder::~Holder() (in /vagrant/Work/waimai/inf_waf/j/build/a.out)
    ==5561== by 0x40091A: main (in /vagrant/Work/waimai/inf_waf/j/build/a.out)
    ==5561== Block was alloc'd at
    ```
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2771 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 85ms UTC 13:00 PVG 21:00 LAX 06:00 JFK 09:00
    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