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

2017-06-27 17:03:22 +08:00
 scinart
#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 友有没有能解答的?

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

结论已经有了,是 Undefined Behavior. 万万没想到。
v2exchen
2017-06-27 17:49:26 +08:00
测试了一下,通过 printf 打印调试,发现系统在执行 v[1].~Holder()的时候先调用了 Holder()。
zts1993
2017-06-27 18:07:14 +08:00
@v2exchen #6 没有吧........///啥编译器?
bp0
2017-06-27 18:43:57 +08:00
考虑一下 vector 空的情况下直接用下标访问会发生什么事情
stormpeach
2017-06-27 19:45:56 +08:00
@xss vector 中内建有内存管理,当 vector 离开它的生存期的时候,它的析构函数会把其中的元素销毁,并释放它们所占用的空间,所以用一般不用显式释放。不过,如果你 vector 中存放的是指针,那么当销毁时,那些指针指向的对象不会被销毁,那些内存不会被释放。
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
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
2017-06-27 22:05:15 +08:00
@dayoushen 按道理是先构造一个临时的,再赋值过去,这样子嘛?
gnaggnoyil
2017-06-28 05:46:08 +08:00
@dayoushen 所以兄弟你看清楚了没人家楼主用的是 emplace_back 而不是 push_back.我建议你先打开-std=c++11 再来说话吧.
ptrvoid
2017-06-28 09:13:02 +08:00
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
2017-06-28 11:12:32 +08:00
@xss 这题跟 emplace_back 没什么关系吧。不用 push_back 是为了省掉拷贝构造函数。
v[1].~Holder(); 手动调用析构函数,指针释放了一次。
main 退出时, ~vector 又会调用 Holder 析构函数。造成指针被多次释放。
这是个未定义行为,我用 vc2015 测试, 是会报异常的。
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
2017-06-28 13:27:30 +08:00
@xss 所以这里哪里有作为 vector 的 element 的(裸)指针出现?
wangjxxx
2017-06-28 13:55:49 +08:00
1 楼正解,double free 没有说一定崩溃吧
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;}这样无论使用者怎么析构都不会面临奔溃的情况,不然生或死由编译器决定。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/371440

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX