本文为大便一箩筐的原创内容,转载请注明出处,谢谢:http://www.cnblogs.com/dbylk/

最近在为公司的项目写内存泄漏定位工具,遇到一些关于C++构造与析构对象的问题,在此记录一下。


一、不要混用 new/delete 和 new[]/delete[]

在默认情况下,也就是不存在 operator new 的重载时,new一个自定义类型 ClassA 的对象时,C++ 会先调用 malloc 来申请一块 sizeof(ClassA) 大小的内存(操作系统会记录这块内存的首地址与大小),然后调用 ClassA 的构造函数在这块内存上初始化对象。此时,new 关键字会返回 malloc 得到的地址。调用delete时,会首先执行 ClassA 的析构函数,再调用 free 释放 malloc 得到的指针。

而new[]则稍微复杂一点,当你调用 new ClassA[nCount] 申请一个对象个数为 nCount 的 ClassA 数组时,编译器(MSVC)会调用 malloc 申请一块大小为 sizeof(ClassA) * nCount + 4 的内存,多出来的 4 bytes 被放在 new[] 关键字返回地址 ptr 的前面,其中记录了数组中元素的个数。当调用 delete[] 删除数组时,会根据数组首地址前 4 bytes 中记录元素的个数来依次调用数组中对象的析构函数(每次指针偏移 sizeof(ClassA) 大小),再调用 free 释放指针 (ptr - 4)。

因此,混用 new/delete 和 new[]/delete[] 通常会导致内存访问崩溃。然后这里用了“通常”,也就是说在某些特定情况下,混用 new/delete 和 new[]/delete[] 是不会有任何影响的:

  1. 创建和释放 C++ 的内建(build-in)类型时,即 int、char等。
  2. 创建和释放“自身和所有成员变量都不含自定义构造函数和析构函数”的类型。(这一条可能依赖于编译器的实现,至少在 MSVC 中此情况成立)

然而当项目代码一旦复杂起来,要分清什么时候上面两个条件能够成立就不是那么轻松的事了,因此最好的方法就是无论何时何地都不要混用 new/delete 和 new[]/delete[]。

二、不要 delete “void” 指针

在整理公司项目代码的过程中,发现有很多地方出现了类似于下面形式的代码:

// Author :大便一箩筐 2016-04-03

struct StructA
{
char cData;
} void* pBuffer = new StructA[nSize];
// do something...
delete pBuffer;

可能写过类似代码的同学会觉得这种写法并没有什么问题,事实也是如此,它能够正常工作,既不会产生内存泄漏,也不会运行报错。

但是,上面情况只能说是一种幸运的巧合,如果发生一些微小的改变,结果就会发生意想不到的变化:

// Author :大便一箩筐 2016-04-03

struct StructA
{
string strData;
} void* pBuffer = new StructA[nSize];
// do something...
delete pBuffer;

细心的同学可能已经看出来了,由于 pBuffer 是 void 指针,delete pBuffer 时,并不会调用 StructA 的析构函数,而这导致了 string 的析构函数也没有被调用,最终产生的结果就是 string 中的字符串缓冲泄漏。

有的同学可能会说,C++ 不是支持多态嘛,我把 StructA 的析构函数定义成虚函数不就好了。然而不幸的是,作为 C++ 的内建类型,void 并没有定义析构函数,因此寄希望于多态是行不通的。

还有的同学可能会说,如果想定义 void 类型的内存缓冲区怎么办?    —— 别忘记我们还有 malloc 和 free。

所以,任何时候都不要尝试 delete void 指针。

三、尽量不要手动调用析构函数

看下面的代码,你能看出程序输出结果是什么吗?

// Author :大便一箩筐 2016-03-31

class Base
{
public:
Base() {};
virtual ~Base()
{
cout << "Base has been destructed. " << endl;
} void Release()
{
this->~Base();
} int baseData;
}; class Derive : public Base
{
public:
Derive() {};
virtual ~Derive()
{
cout << "Derive has been destructed. " << endl;
} void Releaase()
{
this->~Derive();
} int deriveData;
}; int main()
{
Base* pObject = new Derive(); pObject->Release();
delete pObject; system("pause"); return 0;
}

我想很多同学会觉得答案是这个:

Derive has been destructed.

Base has been destructed.

Derive has been destructed.

Base has been destructed.

然而很不幸的,正确答案是这个:

Derive has been destructed.

Base has been destructed.

Base has been destructed.

因为在手动调用 pObject 的析构函数时,虽然 pObject 所指向的内存空间并没有被释放,但执行完 Derive 的析构函数后, pObject 所指向对象的虚函数表指针会从指向派生类 Derive 的虚函数表恢复为指向基类 Base 的虚函数表。

即手动调用析构函数之后,多态指针 pObject 失去了自己的多态特性,此时无法再通过 pObject 直接调用派生类中的虚函数。因此,最后 delete 时只会调用基类的析构函数。

所以,尽量不要在自己写的函数中手动调用析构函数是一个好习惯。

然而上面提到了“尽量”,那就是说事情并没有那么绝对,C++ 支持手动调用析构函数,自然有它的道理:当你需要自己写内存管理器时,手动调用析构函数是必须的。

使用C++为对象分配与释放内存时的几个好习惯的更多相关文章

  1. Linux的虚拟内存管理-如何分配和释放内存,以提高服务器在高并发情况下的性能,从而降低了系统的负载

    Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...

  2. 晓说智能指针shared_ptr为何可以实现跨模块分配和释放内存

    最近做项目, 有个地方是外包人员写的, 其中有个函数,大致这样 void getInfo(std::shared_ptr<Info>& outInfo); 这个函数是一个dll(链 ...

  3. c++ 分配与释放内存

    教学内容: calloc分配内存 calloc与malloc的区别 memset函数初始化内存 free释放动态分配的内存 一.calloc函数分配内存 void *calloc( size_t nu ...

  4. 析构函数释放内存时出现_BLOCK_TYPE_IS_VALID错误

    错误信息截图: 原因: 1.内存泄漏:所以当程序退出时,系统会收回分配的内存,于是调析构函数,由于内存已被错误地释放,于是就会出现"Debug Assertion Failed"的 ...

  5. C++学习011-常用内存分配及释放函数

    C++用有多种方法来分配及释放内存,下面是一些经常使用的内存分配及释放函数 现在我还是一个技术小白,一般用到也指示 new+delete 和 malloc和free 其他的也是在学习中看到,下面的文字 ...

  6. JVM学习-之对象的创建和内存分配

    最近看JVM内存模型,看了很多文章,大都讲到JVM将内存区域划分分:Mehtod-Area(No heap) 方法区,Heap(堆)区,Program Counter Register(程序计数器), ...

  7. DLL函数中内存分配及释放的问题

    DLL函数中内存分配及释放的问题 最近一直在写DLL,遇到了一些比较难缠的问题,不过目前基本都解决了.主要是一些内存分配引起问题,既有大家经常遇到的现象也有特殊的 情况,这里总结一下,做为资料. 错误 ...

  8. Com组件的内存分配和释放,CredentialProvider SHStrDup 字符串拷贝问题

    一.简单介绍 熟悉CredentialProvider的同学应该知道,他为一个Com组件,于是,在这里的内存分配(字符串拷贝)的一系列操作就要依照con的标准来. 二.Com组件的内存分配和释放 CO ...

  9. 如何在MD(d)和MT(d)工程间正确分配和释放动态内存

    MD(d)和MT(d) MD(d)和MT(d)是windows下VC开发的两个编译选项,表示程序的运行时库编译选项. /MT是"multithread, static version&quo ...

随机推荐

  1. ZJOI 2009 假期的宿舍 最大匹配

    主要是main()中的处理,接下来就是二分匹配的模板题了 #include<cstdio> #include<cstring> #define maxn 110 using n ...

  2. 开源BBS论坛软件推荐

    七款开源BBS论坛软件推荐(1) 本文介绍了七个开源的BBS论坛软件(在英文界一般叫做Forum).可能国内的朋友们比较熟悉Discuz!和PHPwind,但其实我们的选择还是很多的,而且下面介绍的这 ...

  3. linux环境上运行.net core 初探

    1.安装 .net core 环境 rpm --import https://packages.microsoft.com/keys/microsoft.ascsh -c 'echo -e " ...

  4. 150. Evaluate Reverse Polish Notation(逆波兰表达式)

    Evaluate the value of an arithmetic expression in Reverse Polish Notation. Valid operators are +, -, ...

  5. POJ - 3648 Wedding (2-SAT 输出解决方案)

    题意:有N-1对夫妇和1对新郎新娘要出席婚礼,这N对人要坐在走廊两侧.要求每对夫妇要坐在不同侧.有M对人有通奸关系,对于这一对人,不能同时坐在新娘对面(新娘新郎也可能和别人有通奸关系).求如何避免冲突 ...

  6. spark-streaming读kafka数据到hive遇到的问题

    在项目中使用spark-stream读取kafka数据源的数据,然后转成dataframe,再后通过sql方式来进行处理,然后放到hive表中, 遇到问题如下,hive-metastor在没有做高可用 ...

  7. ios 常见问题解决 以及小技巧

    1.使用cocoaPods引用第三方类库,报错:file not found   . 解决方案:设置 Project->Info->Configurations之后  clear ,然后再 ...

  8. 20145322何志威 《Java程序设计》课程总结

    课程总结 每周读书笔记链接汇总 •第一周读书笔记 •第二周读书笔记 •第三周读书笔记 •第四周读书笔记 •第五周读书笔记 •第六周读书笔记 •第七周读书笔记 •第八周读书笔记 •第九周读书笔记 •第十 ...

  9. Tornado异步(2)

    Tornado异步 因为epoll主要是用来解决网络IO的并发问题,所以Tornado的异步编程也主要体现在网络IO的异步上,即异步Web请求. 1. tornado.httpclient.Async ...

  10. tcp cubic代码分析

    /* * TCP CUBIC: Binary Increase Congestion control for TCP v2.3 * Home page: * http://netsrv.csc.ncs ...