V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
scinart
V2EX  ›  C

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

  •  2
     
  •   scinart · 2017-06-27 17:03:22 +08:00 · 3380 次点击
    这是一个创建于 2704 天前的主题,其中的信息可能已经有所发展或是发生改变。
    #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.emplace_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   ·   实用小工具   ·   5295 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 07:00 · PVG 15:00 · LAX 23:00 · JFK 02:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.