引言

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

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

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

COuter 和 CInner 类

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

main

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

运行结果分析

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

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

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

一个double free相关问题的澄清的更多相关文章

  1. ZOJ Problem Set - 1331 Perfect Cubes 判断一个double是否为整数

    zju对时间要求比较高,这就要求我们不能简单地暴力求解(三个循环搞定),就要换个思路:因为在循环时,已知a,确定b,c,d,在外重两层循环中已经给定了b和c,我们就不用遍历d,我们可以利用d^3=a^ ...

  2. 给定一个double类型的数组arr,其中的元素可正可负可0,返回子数组累乘的最大乘积。例如arr=[-2.5,4,0,3,0.5,8,-1],子数组[3,0.5,8]累乘可以获得最大的乘积12,所以返回12。

    分析,是一个dp的题目, 设f[i]表示以i为结尾的最大值,g[i]表示以i结尾的最小值,那么 f[i+1] = max{f[i]*arr[i+1], g[i]*arr[i+1],arr[i+1]} ...

  3. java定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积

    需求如下:(1)定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积. (2)定义一个类PassObject,在类中定义一个方法pri ...

  4. impala 四舍五入后转换成string后又变成一个double的数值解决(除不尽的情况)

    impala 四舍五入后转换成string后又变成一个double的数值解决(除不尽的情况)例如Query: select cast(round(2 / 3, 4)*100 as string)+-- ...

  5. 记一个界面刷新相关的Bug

    今天遇到一个比较有意思的bug, 这里简单记录下. Bug的症状是通过拖拉边框把我们客户端主窗口拖小之后,再最大化,会发现窗口显示有问题, 看起来像是刷新问题, 有些地方显示的不对了. 这里要说明的是 ...

  6. 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

    // test14.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include<iostream> #include< ...

  7. 汇编入门——使用DOSBox写一个HelloWorld以及相关软件安装

    0.0.0) 在D盘建立一个ASM文件夹 0.0.1) 放入所需要的文件 1所标示的红色框为必须要存在的文件,要处理汇编文件.百度网盘中下载. 2自己编写的汇编(asm)文件. 3编译汇编自己生成的文 ...

  8. java如何获取一个double的小数位数

    前言 看标题是不是觉得这是一个很简单的问题,我一开始也是这么认为的,但是实际情况下,在各种情况下我们都进行了测试,发现很多实际情况是无法不尽如人意的. 方法分析 当前能想到的比较容易有下面几种 1.直 ...

  9. 分享一个 UiPath Studio 相关的公众号

    RPA 和 UiPath 方面的资料比较少,因此我们自己创建了一个公众号,专门用于传播 UiPath 相关的知识. 会定期发布 UiPath 学习相关的信息.是目前难得的 UiPath 中文资源. 公 ...

随机推荐

  1. [HNOI2008]GT考试 题解

    这题比较难搞.考虑设计状态:\(f_{i,j}\) 表示当前考虑到 \(X_i\) 位,且 \(X\) 的后 \(j\) 位刚好与 \(A\) 列匹配时的方案数.最终答案为 \(\sum_{i=0}^ ...

  2. synchronized锁代码块(七)

    synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...

  3. npx的使用方法、场景

    目录 npx使用教程 npm与npx的概念 npx的使用场景(对比npm的一些优势) 使用场景1: 想用项目中已经安装好的某个包, 但是不能直接执行(因为没有全局安装, 涉及环境变量的问题) 使用场景 ...

  4. Go通关04:正确使用 array、slice 和 map!

    Array(数组) 数组存放的是固定长度.相同类型的数据. 数组声明 var <数组名> = [<长度>]<元素>{元素1,元素2} var arr = [2]in ...

  5. String,String Builder,String Buffer-源码

    目录 String 源码分析 常用的API isEmpty() length() charAt() substring() equals() equals()与"==" inter ...

  6. 关于C语言中对数字的扩展和缩短

    关于对数字的扩展:如果需要在不改变他的类型的情况下去扩展一个数字 有符号数字: 如果最高位为0---向左按位复制0 如果最高位为1---向左按位复制1 无符号数字:向左按位复制0即可 对于数字的缩短: ...

  7. 云服务器是什么?ECS、BCC、CVM...

    什么是云服务器?云服务器有哪些优势?能用来干什么? 很多人不太了解云服务器的定义和用途. 云服务器是一种简单高效.处理能力可弹性伸缩的计算服务,帮助用户快速构建更稳定.安全的应用,提升运维效率,降低 ...

  8. Session与Cookie的原理以及使用小案例>从零开始学JAVA系列

    目录 Session与Cookie的原理以及使用小案例 Cookie和Session所解决的问题 Session与Cookie的原理 Cookie的原理 Cookie的失效时机 小提示 Session ...

  9. 使用vue实现简单的待办事项

    待办事项 效果图 目录结构 详细代码 AddNew.vue <template> <div> <input v-model="content"/> ...

  10. C++ 继承方式 与 普通方式 对比

    1 //C++ 继承 2 //继承是面向对象三大特性之一 3 4 #include <iostream> 5 #include <string> 6 using namespa ...