浅析C++内存分配与释放操作过程——三种方式可以分配内存new operator, operator new,placement new
引言:C++中总共有三种方式可以分配内存,new operator, operator new,placement new。
一,new operator
这就是我们最常使用的 new 操作符。查看汇编码可以看出:它不是一个函数,所以没有堆栈信息,而且它不能被重载。
请看下面一段代码:
- #include <iostream>
- class A
- {
- public:
- A(int x):m_x(x)
- {
- std::cout << "constructor of A" << std::endl;
- }
- ~A()
- {
- std::cout << "destructor of A" << std::endl;
- }
- private:
- int m_x;
- };
- int main(int argc, char* argv[])
- {
- A *pa = new A(1);
- delete pa;
- return 0;
- }
调用 A *pa = new A(1); 的过程共总分为三步
1,调用 void* operator new(size_t size)分配sizeof(A)大小的内存;
2,在第一步返回的地址上调用A的构造函数;
3,将第一步返回的地址赋值给pa;
与 new operator 对应的是 delete operator,它也是操作符,同样不能被重载。
调用 delete pa;的过程大致分为两步
1,在 pa 所指的地址上调用A类的析构函数;
2,调用void operator delete(void *pUserData)函数释放pa所指内存;
如果A类没有声明析构函数,编译器也没有不要合成析构函数,上述delete过程就只有第二步。
对基本数据类型也只有第二部。
现在思考一个问题:考虑上诉代码,下面的代码会有内存泄漏吗?
void *pvoid = pa;
delete pvoid;
上述问题可以表述为执行 delete pa 时释放的是sizeof(A)的内存吗?
答案是肯定的,因为不管是 delete pa 还是 delete pvoid 最后执行的都是 void operator delete(void *pUserData),
唯一的区别是delete pvoid 时不会调用A类的构造函数。
二,operator new
这个函数是 new opertor 必定会调用的,其定义为
- void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
- { // try to allocate size bytes
- void *p;
- while ((p = malloc(size)) == 0)
- if (_callnewh(size) == 0)
- { // report no memory
- static const std::bad_alloc nomem;
- _RAISE(nomem);
- }
- return (p);
- }
其中 _RAISE 宏定义为
#define _RAISE(x) throw x
可以看出,这个函数最终会调用C语言中的 malloc 函数,而且分配内存失败的话会抛出 std::bad_alloc 异常。
与之对应的是 operator delete,其定义为
- void operator delete(
- void *pUserData
- )
- {
- _CrtMemBlockHeader * pHead;
- RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
- if (pUserData == NULL)
- return;
- _mlock(_HEAP_LOCK); /* block other threads */
- __TRY
- /* get a pointer to memory block header */
- pHead = pHdr(pUserData);
- /* verify block type */
- _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
- _free_dbg( pUserData, pHead->nBlockUse );
- __FINALLY
- _munlock(_HEAP_LOCK); /* release other threads */
- __END_TRY_FINALLY
- return;
- }
可以看出,这个函数会调用 _free_dgb。再看看C语言中的 free 函数定义
extern "C" _CRTIMP void __cdecl free(
void * pUserData
)
{
_free_dbg(pUserData, _NORMAL_BLOCK);
}
所以,operator delete 最终的释放过程和 free 函数相同。
而且这两个函数都可以被重载
- #include <iostream>
- class A
- {
- public:
- A(int x):m_x(x)
- {
- std::cout << "constructor of A" << std::endl;
- }
- ~A()
- {
- std::cout << "destructor of A" << std::endl;
- }
- void* operator new(size_t size)
- {
- std::cout << "operator new of A" << std::endl;
- return ::operator new(size);
- }
- void operator delete(void* pUserData)
- {
- std::cout << "operator delete of A" << std::endl;
- ::operator delete(pUserData);
- }
- private:
- int m_x;
- };
- int main(int argc, char* argv[])
- {
- A *pa = new A(1);
- delete pa;
- return 0;
- }
需要注意的是,重载的时候要能保证其原有的行为,而且重载时不管有没有加static,这两个函数都是static类型的。
现在思考两个问题:
1,如何保证一个类不能在堆上创建。(比如考虑到性能因素)
2,如何保证一个类对象不能被delete。(比如有时候全局作用域的单一实例不能被delete)
答案是只需将类的operator new 和 operator delete 重载为 非public 类型的就可以了。这样的话这两行代码
- A *pa = new A(1);
- delete pa;
根本不能通过编译。
顺便提另一个问题:如果保证一个类不能在栈上创建?
答案同样是是将该类的析构函数声明为非 public 类型的就可以了。
这样的话即便是创建该类的 global 对象也会导致编译失败。
三,placement new
这是一个全局函数,其定义为:
- inline void *__CRTDECL operator new(size_t, void *_Where) _THROW0()
- { // construct array with placement at _Where
- return (_Where);
- }
它同样可以被重载
- class A
- {
- public:
- A(int x):m_x(x)
- {
- std::cout << "constructor of A" << std::endl;
- }
- ~A()
- {
- std::cout << "destructor of A" << std::endl;
- }
- void *operator new(size_t size)
- {
- std::cout << "operator new of A" << std::endl;
- return ::operator new(size);
- }
- void* operator new(size_t size, void* p)
- {
- std::cout << "placement new of A" << std::endl;
- return ::operator new(size, p);
- }
- void operator delete(void* pUserData)
- {
- std::cout << "operator delete of A" << std::endl;
- ::operator delete(pUserData);
- }
但需要注意的是,如果只重载了 void *operator new(size_t size) ,调用placement new 的时候将会调到编译错误。
同样,如果如果只重载了 void *operator new(size_t size, void*p), 执行new 操作 的时候也会调到编译错误。
placement new 的执行忽略了size_t参数,只返还第二个参数。其结果是允许用户把一个对象放到一个特定的地方,达到调用构造函数的效果。
和其他普通的new不同的是,它在调用时括号里多了另外一个参数。比如:
- void *ptr = operator new(sizeof(A)); //operator new
- A *pa = new(ptr)A(2); //placement new
- pa->~A();
括 号里的参数ptr是一个指针,它指向一个内存缓冲器,placement new 将在这个缓冲器上分配一个对象。
placement new 的返回值是这 个被构造对象的地址(比如括号中的传递参数)。
placement new是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,也没有对应的delete函数。
但需要调用对象的析构函数。因此,最后应该需要执行pa->~A();
placement new主要适用于:
1,在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定 的;
2,长时间运行而不被打断的程序;
3,执行一个垃圾收集器 (garbage collector)。
还有一点需要注意的是,看如下代码中:
- class A
- {
- public:
- A(int x = 1):m_x(x){}
- ~A()
- {
- }
- private:
- int m_x;
- };
- int main(int argc, char* argv[])
- {
- const int N = 8;
- void *ptr = operator new(sizeof(A) * N);
- A *pa = new(ptr)A[N];
- return 0;
- }
这是一段隐藏巨大错误的代码,很容易造成运行时内存错误。
因为使用placement new分配自定义对象的数组时,如果该类定义了析构函数(这点很重要),应该多分配4个字节用于存储元素个数。
所以上述main函数应该改成
- int main(int argc, char* argv[])
- {
- const int N = 8;
- void *ptr = operator new(sizeof(A) * N + sizeof(int));
- A *pa = new(ptr)A[N];
- return 0;
- }
下面是我调试时的截图。
ptr指向内存:

pa指向内存:

从中可以看出ptr所指内存的头4个字节存储元素个数。pa 实际指向 ptr + 4 的地址。
总结:
1,new 和 delete 最终都会调用C语言中的 malloc 和 free 过程。
2,想要将对象构造在指定的内存地址上时可以使用placement new,但使用时要格外小心。
3,只想分配指定大小的内存而不构造对象时,可以用operator new 取代 new operator。
4,可以重载类的operator new 来跟踪类对象在堆上创建的过程及总数。
5,在C++中应该尽量使用new operator,因为这个操作符自动检查需要分配的内存大小。
http://blog.csdn.net/passion_wu128/article/details/9150261
浅析C++内存分配与释放操作过程——三种方式可以分配内存new operator, operator new,placement new的更多相关文章
- Linux就这个范儿 第15章 七种武器 linux 同步IO: sync、fsync与fdatasync Linux中的内存大页面huge page/large page David Cutler Linux读写内存数据的三种方式
Linux就这个范儿 第15章 七种武器 linux 同步IO: sync.fsync与fdatasync Linux中的内存大页面huge page/large page David Cut ...
- Linux就这个范儿 第18章 这里也是鼓乐笙箫 Linux读写内存数据的三种方式
Linux就这个范儿 第18章 这里也是鼓乐笙箫 Linux读写内存数据的三种方式 P703 Linux读写内存数据的三种方式 1.read ,write方式会在用户空间和内核空间不断拷贝数据, ...
- 对象的notify方法的含义和对象锁释放的三种情况
1,notify的含义 (1)notify一次只随机通知一个线程进行唤醒 (2)在执行了notify方法之后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获得该对象锁, 要等到 ...
- JavaScript声明全局变量的三种方式
JavaScript声明全局变量的三种方式 JS中声明全局变量主要分为显式声明或者隐式声明下面分别介绍. 声明方式一: 使用var(关键字)+变量名(标识符)的方式在function外部声明,即为 ...
- C++创建对象的三种方式
C++在创建对象的时候,有三种方式: #include <iostream> using namespace std; class A { private: int n; public: ...
- [UE4]C++创建对象的三种方式
#include <iostream> using namespace std; class A { private: int n; public: A(int m):n(m) { } ~ ...
- C#批量插入数据到Sqlserver中的三种方式
本篇,我将来讲解一下在Sqlserver中批量插入数据. 先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的是GUID,表中没有创建任何索引.GUID必然是比自增长要快的,因为你生 成 ...
- js学习-DOM之动态创建元素的三种方式、插入元素、onkeydown与onkeyup两个事件整理
动态创建元素的三种方式: 第一种: Document.write(); <body> <input type="button" id="btn" ...
- 三种方式上传文件-Java
前言:负责,因为该项目他(jetty嵌入式开始SpringMvc)实现文件上传的必要性,并拥有java文件上传这一块还没有被曝光.并 Http 更多晦涩协议.因此,这种渐进的方式来学习和实践上载文件的 ...
随机推荐
- ASP.NET Core Web开发学习笔记-1介绍篇
ASP.NET Core Web开发学习笔记-1介绍篇 给大家说声报歉,从2012年个人情感破裂的那一天,本人的51CTO,CnBlogs,Csdn,QQ,Weboo就再也没有更新过.踏实的生活(曾辞 ...
- 菜鸟的jQuery源码学习笔记(二)
jQuery对象是使用构造函数和原型模式相结合的方式创建的.现在来看看jQuery的原型对象jQuery.prototype: jQuery.fn = jQuery.prototype = { //成 ...
- python 文件移动(shutil)
# encoding=utf-8 # /home/bergus/tongbu/360共享/编程语言 # /home/bergus/桌面 # /home/bergus/test/hh import os ...
- 定义file input
<div class="inputFileWrapper"> <label for="inputFile"> <input typ ...
- hdu 5500 Reorder the Books(规律)
题意: 有一个1→n的排列形成的数列,我们要用最少的操作次数把这个数列从小到大排序,每次操作都是把一个数放到整个数列的最前面. 思路: 首先最大的数n是不用操作的(其他数操作好了,n ...
- bzoj 维护序列seq(双标记线段树)
Seq 维护序列seq Time Limit: 30 Sec Memory Limit: 64 MBSubmit: 4184 Solved: 1518[Submit][Status][Discus ...
- pomelo初探
最近发现了一个比较好玩的东西pomelo.地址:点击打开链接 这个东西是网易开发的一套基于node.js的高性能,分布式游戏服务器框架.这套框架不仅可以用来开发游戏服务器,也可用于开发高实时web应用 ...
- cocos2d js 怎样动态载入外部图片
官网没有详细样例,仅仅有看api,研究成果例如以下 var that = this; var url = "http://xxxxxx"; cc.loader.loadImg(ur ...
- .NET与你若仅仅如初见(一)
难忘初次见到你,那是一个夏日的午后,可是天空中乌云密布.大雨来临前的一段时间总是非常闷热的,当我朦胧的睡眼看到你之后瞬间就清醒了,感觉空气也凉爽了起来.尽管仅仅一眼但就是被你那清新脱俗沉鱼落雁之美所征 ...
- 高性能浏览器网络(High Performance Browser Networking) 第二章
第2章 TCP篇 互联网的核心是两个协议,IP和TCP. IP也叫Internet协议,提供主机到主机的路由和寻址:TCP,传输控制协议,在不可靠的传输通道上提供一个可靠的网络抽象.TCP / IP协 ...