#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 友有没有能解答的?
1
bombless 2017-06-27 17:12:42 +08:00 1
double free 是 UB 吧,UB 就是…… UB ……
|
2
liuzhedash 2017-06-27 17:19:09 +08:00
是否崩是操作系统钦定的,当然代码的决定权也很重要
|
3
xss 2017-06-27 17:19:29 +08:00
为啥要崩?并没有看到 double free.
你的意思是, 你指望 Vector 在结束生命周期的时候自动调用里面元素的析构函数? |
4
wwqgtxx 2017-06-27 17:30:37 +08:00
你可以再析构函数中输出看一看析构函数到底调用了几次
|
5
scinart OP 不小心给自己点了个赞,还不知道怎么撤销,也不知道怎么添加附言。
结论已经有了,是 Undefined Behavior. 万万没想到。 |
6
v2exchen 2017-06-27 17:49:26 +08:00
测试了一下,通过 printf 打印调试,发现系统在执行 v[1].~Holder()的时候先调用了 Holder()。
|
8
bp0 2017-06-27 18:43:57 +08:00 via Android
考虑一下 vector 空的情况下直接用下标访问会发生什么事情
|
9
stormpeach 2017-06-27 19:45:56 +08:00
@xss vector 中内建有内存管理,当 vector 离开它的生存期的时候,它的析构函数会把其中的元素销毁,并释放它们所占用的空间,所以用一般不用显式释放。不过,如果你 vector 中存放的是指针,那么当销毁时,那些指针指向的对象不会被销毁,那些内存不会被释放。
|
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; } |
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 |
13
gnaggnoyil 2017-06-28 05:46:08 +08:00
@dayoushen 所以兄弟你看清楚了没人家楼主用的是 emplace_back 而不是 push_back.我建议你先打开-std=c++11 再来说话吧.
|
14
ptrvoid 2017-06-28 09:13:02 +08:00
|
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. ``` |
16
enenaaa 2017-06-28 11:12:32 +08:00
@xss 这题跟 emplace_back 没什么关系吧。不用 push_back 是为了省掉拷贝构造函数。
v[1].~Holder(); 手动调用析构函数,指针释放了一次。 main 退出时, ~vector 又会调用 Holder 析构函数。造成指针被多次释放。 这是个未定义行为,我用 vc2015 测试, 是会报异常的。 |
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. |
18
gnaggnoyil 2017-06-28 13:27:30 +08:00
@xss 所以这里哪里有作为 vector 的 element 的(裸)指针出现?
|
19
wangjxxx 2017-06-28 13:55:49 +08:00
1 楼正解,double free 没有说一定崩溃吧
|
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;}这样无论使用者怎么析构都不会面临奔溃的情况,不然生或死由编译器决定。
|
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 就放在那里构造的对象.... |
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 ``` |