一个double free相关问题的澄清

引言

前一阵定位 Oracle 的 OCI 接口相关的一个内存释放问题,在网上看到了链接如下的这篇文章:

一个C++bug引入的许多知识

看到后面说 vector 里的两个单元里的内部成员指针地址是一样的,感觉很怪异。于是写了个简单程序实际验证一下。

COuter 和 CInner 类

 1 #include <stdio.h>
 2 #include <vector>
 3 
 4 class CInner
 5 {
 6 public:
 7     CInner(int id) : m_nId(id) {
 8         printf("  Inner item %d constructor\n", id);
 9     }
10     ~CInner() {
11         printf("  Inner item 0x%X deconstructor\n", m_nId);
12     }
13 private:
14     int m_nId;
15 };
16 
17 class COuter
18 {
19 public:
20     COuter(int id) : m_pInner(NULL), m_nId(id) {
21         printf("Outer item %d constructor\n", id);
22     }
23     ~COuter() {
24         printf("Outer item %d deconstructor\n", m_nId);
25         if (m_pInner) {
26             printf("Inner item %d with address=0x%X will be deleted...\n", m_nId, m_pInner);
27             delete m_pInner;
28             printf("Inner item %d deleting done.\n\n", m_nId);
29         }
30         else {
31             printf("Item %d has no inner item.\n\n", m_nId);
32         }
33     }
34 
35     CInner* getInnerItem() {
36         if (NULL == m_pInner) {
37             m_pInner = new CInner(m_nId);
38             printf("A inner item with id=%d and address=0x%X is created.\n", m_nId, m_pInner);
39         }
40         return m_pInner;
41     }
42 private:
43     CInner* m_pInner;
44     int m_nId;
45 };

main

 1 int main()
 2 {
 3     std::vector<COuter> vec;
 4     for (int idx = 0; idx < 2; ++idx) {
 5         COuter item(idx);
 6         printf("Outer item %d will be pushed into vector...\n", idx);
 7         vec.push_back(item);
 8         printf("Outer item %d is in vector.\n", idx);
 9         vec.back().getInnerItem();
10     }
11     return 0;
12 }

运行结果分析

程序本身很简单,以下以 Windows 平台为例(Linux 下情形类似)就运行输出结果进行分析。

程序运行输出以下信息后报错:

 1 Outer item 0 constructor // 局部变量COuter item(0)开始生命周期,其构造函数被调用,这是第一个id为0的Outer实例
 2 Outer item 0 will be pushed into vector...
 3 Outer item 0 is in vector. // 此时有两个Outer实例,id都为0,第二个是第一个的拷贝构造,并已放入vec里
 4   Inner item 0 constructor // 为第二个Outer实例构造inner实例
 5 A inner item with id=0 and address=0xD55D98 is created.
 6 Outer item 0 deconstructor // COuter item(0)到了生命周期,其析构函数被调用,之后只剩第二个id为0的Outer实例
 7 Item 0 has no inner item.
 8 
 9 Outer item 1 constructor // 局部变量COuter item(1)开始生命周期
10 Outer item 1 will be pushed into vector...
11 Outer item 0 deconstructor
12 Inner item 0 with address=0xD55D98 will be deleted...
13   Inner item 0x0 deconstructor
14 Inner item 0 deleting done.
15 
16 Outer item 1 is in vector.
17   Inner item 1 constructor
18 A inner item with id=1 and address=0xD55D50 is created.
19 Outer item 1 deconstructor
20 Item 1 has no inner item.
21 
22 Outer item 0 deconstructor
23 Inner item 0 with address=0xD55D98 will be deleted...
24   Inner item 0xFEEEFEEE deconstructor

 第11行到第14行的输出信息说明:vec里因为要增加新单元,其内存空间要做调整,第三个id为0的Outer实例由第二个id为0的Outer实例拷贝构造而成并加入vec,随后便释放了第二个id为0的Outer实例。由于缺省拷贝函数会对指针成员做简单值拷贝,第三个id为0的Outer实例里的m_pInner指针指向的是第二个id为0的Outer实例所指向的Inner实例,即两个Outer实例内部指向了同一个Inner实例。而这个Inner实例随着第二个id为0的Outer实例的释放而被释放了,第三个id为0的实例还在指向这个被释放了的Inner实例,这就留下了隐患,因为。

第16行到20行的输出,很好理解,此时vec里有两个单元,其中id为1的单元实例所指向的Inner实例也已创建好了。两个Inner实例的所占堆空间的起始地址是不一样的,即0xD55D98和0xD55D50。问题在于0xD55D98所指向的地址已经释放过了。

 第22行的输出说明:局部变量vec到了生命周期,在释放第一个单元实例时,由于其指针成员不为NULL,于是对0xD55D98所指空间又做一次释放,从而引发了问题。

 

posted on 2020-10-30 11:02  readalps  阅读(453)  评论(0)    收藏  举报

导航