C++ 内存分配(new,operator new)面面观 (转)
本文主要讲述C++ new运算符和operator new, placement new之间的种种关联,new的底层实现,以及operator new的重载和一些在内存池,STL中的应用。
一 new运算符和operator new():
operator new
can be called explicitly as a regular function, but in C++, new
is an operator with a very specific behavior: An expression with the new
operator, first calls function operator new
(i.e., this function) with the size of its type specifier as first argument, and if this is successful, it then automatically initializes or constructs the object (if needed). Finally, the expression evaluates as a pointer to the appropriate type.- <span style="font-family:SimHei;font-size:14px;">//平台:Visual Stdio 2008
- #include<iostream>
- class A
- {
- public:
- A()
- {
- std::cout<<"call A constructor"<<std::endl;
- }
- ~A()
- {
- std::cout<<"call A destructor"<<std::endl;
- }
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- A* a = new A;
- delete a;
- system("pause");
- return 0;
- }</span>
下面我们跟踪一下A反汇编代码,由于Debug版本反汇编跳转太多,因此此处通过Release版本在A* a = new A;处设断点反汇编:
- <span style="font-family:SimHei;font-size:14px;"> A* a = new A;
- 01301022 push 1 ;不含数据成员的类占用一字节空间,此处压入sizeof(A)
- 01301024 call operator new (13013C2h) ;调用operator new(size_t size)
- 01301029 mov esi,eax ;返回值保存到esi
- 0130102B add esp,4 ;平衡栈
- 0130102E mov dword ptr [esp+8],esi ;
- 01301032 mov dword ptr [esp+14h],0
- 0130103A test esi,esi ;在operator new之后,检查其返回值,如果为空(分配失败),则不调用A()构造函数
- 0130103C je wmain+62h (1301062h) ;为空 跳过构造函数部分
- 0130103E mov eax,dword ptr [__imp_std::endl (1302038h)] ;构造函数内部,输出字符串
- 01301043 mov ecx,dword ptr [__imp_std::cout (1302050h)]
- 01301049 push eax
- 0130104A push offset string "call A constructor" (1302134h)
- 0130104F push ecx
- 01301050 call std::operator<<<std::char_traits<char> > (13011F0h)
- 01301055 add esp,8
- 01301058 mov ecx,eax
- 0130105A call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1302040h)]
- 01301060 jmp wmain+64h (1301064h) ;构造完成,跳过下一句
- 01301062 xor esi,esi ;将esi置空,这里的esi即为new A的返回值
- 01301064 mov dword ptr [esp+14h],0FFFFFFFFh
- delete a;
- 0130106C test esi,esi ;检查a是否为空
- 0130106E je wmain+9Bh (130109Bh) ;如果为空,跳过析构函数和operator delete
- 01301070 mov edx,dword ptr [__imp_std::endl (1302038h)] ;析构函数 输出字符串
- 01301076 mov eax,dword ptr [__imp_std::cout (1302050h)]
- 0130107B push edx
- 0130107C push offset string "call A destructor" (1302148h)
- 01301081 push eax
- 01301082 call std::operator<<<std::char_traits<char> > (13011F0h)
- 01301087 add esp,8
- 0130108A mov ecx,eax
- 0130108C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1302040h)]
- 01301092 push esi ;压入a
- 01301093 call operator delete (13013BCh) ;调用operator delete
- 01301098 add esp,4
- 通过反汇编可以看出A* = new A包含了operator new(sizeof(A))和A()两个步骤(当然,最后还要将值返回到a)
- delete a包含了~A()和operator delete(a)两个步骤。
- </span>
二 operator new的三种形式:
throwing (1) |
void* operator new (std::size_t size) throw (std::bad_alloc); |
---|---|
nothrow (2) |
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw(); |
placement (3) |
void* operator new (std::size_t size, void* ptr) throw(); |
- <span style="font-family:Microsoft YaHei;font-size:14px;">#ifndef __PLACEMENT_NEW_INLINE
- #define __PLACEMENT_NEW_INLINE
- inline void *__cdecl operator new(size_t, void *_P)
- {return (_P); }
- #if _MSC_VER >= 1200
- inline void __cdecl operator delete(void *, void *)
- {return; }
- #endif
- #endif</span>
- <span style="font-family:SimHei;font-size:14px;">#include <iostream>
- class A
- {
- public:
- A()
- {
- std::cout<<"call A constructor"<<std::endl;
- }
- ~A()
- {
- std::cout<<"call A destructor"<<std::endl;
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- A* p = (A*)::operator new(sizeof(A)); //分配
- new(p) A(); //构造
- p->~A(); //析构
- ::operator delete(p); //释放
- system("pause");
- return 0;
- }</span>
- <span style="font-family:SimHei;font-size:14px;"> A* a = (A*)::operator new(sizeof(A)); //分配
- 00F9151D push 1
- 00F9151F call operator new (0F91208h) ;调用::operator new(size_t size)也就是throwing(1)版本
- 00F91524 add esp,4
- 00F91527 mov dword ptr [ebp-14h],eax ;返回地址放入[ebp-14h] 即为p
- new(a) A(); //构造
- 00F9152A mov eax,dword ptr [ebp-14h]
- 00F9152D push eax
- 00F9152E push 1 ;压入p
- 00F91530 call operator new (0F91280h);调用operator new(size_t, void* p)即placement(3)版本 只是简单返回p
- 00F91535 add esp,8
- 00F91538 mov dword ptr [ebp-0E0h],eax ;将p放入[ebp-0E0h]
- 00F9153E mov dword ptr [ebp-4],0
- 00F91545 cmp dword ptr [ebp-0E0h],0 ;判断p是否为空
- 00F9154C je wmain+81h (0F91561h) ;如果为空 跳过构造函数
- 00F9154E mov ecx,dword ptr [ebp-0E0h] ;取出p到ecx
- 00F91554 call A::A (0F91285h) ;调用构造函数 根据_thiscall调用约定 this指针通过ecx寄存器传递
- 00F91559 mov dword ptr [ebp-0F4h],eax ;将返回值(this指针)放入[ebp-0F4h]中
- 00F9155F jmp wmain+8Bh (0F9156Bh) ;跳过下一句
- 00F91561 mov dword ptr [ebp-0F4h],0 ;将[ebp-0F4h]置空 当前面判断p为空时执行此语句
- 00F9156B mov ecx,dword ptr [ebp-0F4h] ;[ebp-0F4h]为最终构造完成后的this指针(或者为空) 放入ecx
- 00F91571 mov dword ptr [ebp-0ECh],ecx ;又将this放入[ebp-0ECh] 这些都是调试所用
- 00F91577 mov dword ptr [ebp-4],0FFFFFFFFh
- a->~A(); //析构
- 00F9157E push 0
- 00F91580 mov ecx,dword ptr [ebp-14h] ;从[ebp-14h]中取出p
- 00F91583 call A::`scalar deleting destructor' (0F91041h) ;调用析构函数(跟踪进去比较复杂 如果在Release下,构造析构函数都是直接展开的)
- ::operator delete(a); //释放
- 00F91588 mov eax,dword ptr [ebp-14h] ;将p放入eax
- 00F9158B push eax ;压入p
- 00F9158C call operator delete (0F910B9h);调用operator delete(void* )
- 00F91591 add esp,4 </span>
- <span style="font-family:SimHei;font-size:14px;"> A* a = (A*)::operator new(sizeof(A)); //分配
- 010614FE push 1
- 01061500 call operator new (1061208h)
- 01061505 add esp,4
- 01061508 mov dword ptr [a],eax
- //new(a) A(); //构造
- a->A::A();
- 0106150B mov ecx,dword ptr [a]
- 0106150E call operator new (1061285h)
- a->~A(); //析构
- 01061513 push 0
- 01061515 mov ecx,dword ptr [a]
- 01061518 call A::`scalar deleting destructor' (1061041h)
- ::operator delete(a); //释放
- 0106151D mov eax,dword ptr [a]
- 01061520 push eax
- 01061521 call operator delete (10610B9h)
- 01061526 add esp,4 </span>
三 operator new重载:
1.在类中重载operator new
- <span style="font-family:SimHei;font-size:14px;">#include <iostream>
- class A
- {
- public:
- A()
- {
- std::cout<<"call A constructor"<<std::endl;
- }
- ~A()
- {
- std::cout<<"call A destructor"<<std::endl;
- }
- void* operator new(size_t size)
- {
- std::cout<<"call A::operator new"<<std::endl;
- return malloc(size);
- }
- void* operator new(size_t size, const std::nothrow_t& nothrow_value)
- {
- std::cout<<"call A::operator new nothrow"<<std::endl;
- return malloc(size);
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- A* p1 = new A;
- delete p1;
- A* p2 = new(std::nothrow) A;
- delete p2;
- system("pause");
- return 0;
- }</span>
- <span style="font-family:SimHei;font-size:14px;">void* ::operator new(size_t size)
- {
- std::cout<<"call global operator new"<<std::endl;
- return malloc(size);
- }</span>
error C2660: “A::operator new”: 函数不接受 2 个参数。
- <span style="font-family:Microsoft YaHei;font-size:14px;">void* operator new(size_t size, int x, int y, int z)
- {
- std::cout<<"X="<<x<<" Y="<<y<<" Z="<<z<<std::endl;
- return malloc(size);
- }</span>
2.重载全局operator new
3.类中operator new和全局operator new的调用时机
四 operator new运用技巧和一些实例探索
1.operator new重载运用于调试:
- <span style="font-family:SimHei;font-size:14px;">//A.h
- class A
- {
- public:
- A()
- {
- std::cout<<"call A constructor"<<std::endl;
- }
- ~A()
- {
- std::cout<<"call A destructor"<<std::endl;
- }
- void* operator new(size_t size, const char* file, int line)
- {
- std::cout<<"call A::operator new on file:"<<file<<" line:"<<line<<std::endl;
- return malloc(size);
- return NULL;
- }
- };
- //Test.cpp
- #include <iostream>
- #include "A.h"
- #define new new(__FILE__, __LINE__)
- int _tmain(int argc, _TCHAR* argv[])
- {
- A* p1 = new A;
- delete p1;
- A* p2 = new A;
- delete p2;
- system("pause");
- return 0;
- }</span>
2.内存池优化
3.STL中的new
五 delete的使用
- <span style="font-family:SimHei;font-size:14px;">void* operator new(size_t size, int x)
- {
- cout<<" x = "<<x<<endl;
- return malloc(size);
- }
- void operator delete(void* p, int x)
- {
- cout<<" x = "<<x<<endl;
- free(p);
- }</span>
六 关于new和内存分配的其他
1.set_new_handler
- <span style="font-family:SimHei;font-size:14px;">#include <iostream>
- #include <new.h>// 使用_set_new_mode和set_new_handler
- void nomem_handler()
- {
- std::cout<<"call nomem_handler"<<std::endl;
- }
- int main()
- {
- _set_new_mode(1); //使new_handler有效
- set_new_handler(nomem_handler);//指定入口函数 函数原型void f();
- std::cout<<"try to alloc 2GB memory...."<<std::endl;
- char* a = (char*)malloc(2*1024*1024*1024);
- if(a)
- std::cout<<"ok...I got it"<<std::endl;
- free(a);
- system("pause");
- }</span>
2.new分配数组
- <span style="font-family:SimHei;font-size:14px;">//A.h
- class A
- {
- public:
- A()
- {
- std::cout<<"call A constructor"<<std::endl;
- }
- ~A()
- {
- std::cout<<"call A destructor"<<std::endl;
- }
- void* operator new(size_t size)
- {
- std::cout<<"call A::operator new[] size:"<<size<<std::endl;
- return malloc(size);
- }
- void operator delete[](void* p)
- {
- std::cout<<"call A::operator delete[]"<<std::endl;
- free(p);
- }
- void operator delete(void* p)
- {
- free(p);
- }
- };</span>
- <span style="font-family:SimHei;font-size:14px;">//Test.cpp
- #include <iostream>
- #include "A.h"
- void* operator new[](size_t size)
- {
- std::cout<<"call global new[] size: "<<size<<std::endl;
- return malloc(size);
- }
- void operator delete[](void* p)
- {
- std::cout<<"call global delete[] "<<std::endl;
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- std::cout<<"sizeof A "<<sizeof(A)<<std::endl;
- A* p1 = new A[3];
- delete []p1;
- system("pause");
- return 0;
- }</span>
简单跟踪了一下:
参考文献:
C++ 内存分配(new,operator new)面面观 (转)的更多相关文章
- 【转】C++ 内存分配(new,operator new)详解
本文主要讲述C++ new运算符和operator new, placement new之间的种种关联,new的底层实现,以及operator new的重载和一些在内存池,STL中的应用. 一 new ...
- 浅析C++内存分配与释放操作过程——三种方式可以分配内存new operator, operator new,placement new
引言:C++中总共有三种方式可以分配内存,new operator, operator new,placement new. 一,new operator 这就是我们最常使用的 new 操作符.查看汇 ...
- C++ 内存分配(new,operator new)详解
参考:C++ 内存分配(new,operator new)详解 如何限制对象只能建立在堆上或者栈上 new运算符和operator new() new:指我们在C++里通常用到的运算符,比如A* a ...
- 源码分析:Java对象的内存分配
Java对象的分配,根据其过程,将其分为快速分配和慢速分配两种形式,其中快速分配使用无锁的指针碰撞技术在新生代的Eden区上进行分配,而慢速分配根据堆的实现方式.GC的实现方式.代的实现方式不同而具有 ...
- C++内存分配方式详解——堆、栈、自由存储区、全局/静态存储区和常量存储区
栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区.里面的变量通常是局部变量.函数参数等.在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用.和堆一样 ...
- 【转载】C++内存分配
原文:C++内存分配 内存泄露相信对C++程序员来说都不陌生.解决内存泄露的方案多种多样,大部分方案以追踪检测为主,这种方法实现起来容易,使用方便,也比较安全. 首先我们要确定这个模块的主要功能: 能 ...
- 转:内存区划分、内存分配、常量存储区、堆、栈、自由存储区、全局区[C++][内存管理][转载]
内存区划分.内存分配.常量存储区.堆.栈.自由存储区.全局区[C++][内存管理][转载] 一. 在c中分为这几个存储区1.栈 - 由编译器自动分配释放2.堆 - 一般由程序员分配释放,若程序员不释放 ...
- 转:c++内存分配
第一篇: http://my.oschina.net/pollybl1255/blog/140323 BSS段:(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS ...
- 内存分配(c/c++)
C++中内存分配 内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和常量存储区. 1,栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区.里面的 ...
随机推荐
- insertBefore方法(javascript与jQuery)
说到insertBefore()方法,其实javascript与jQuery中都有此方法,那么他们用法是否相同呢? 其实,还是有点区别的.反正我是爱搞混淆了,先做个小笔记吧! 1.insertBefo ...
- UWP: 通过命令行启动 UWP 应用
最近在开发应用的过程中,我遇到了如标题所述的需求,其实主要是为了能够快捷启动应用,正像我们可以在"运行"对话框中可以输入一些可执行程序的名称后,就能够直接启动它:这样做,可以增加 ...
- PHP UEditor富文本编辑器 显示 后端配置项没有正常加载,上传插件不能正常使用
UEditor是由百度web前端研发部开发所见即所得富文本web编辑器,具有轻量,可定制,注重用户体验等特点,开源基于MIT协议,允许自由使用和修改代码... 问题描述 我的编辑器在本地测试的时候没问 ...
- 【问题解决】java中为什么不建议使用DataInputStream 的readLine()方法
常用方法 int read(byte[] b) 从包含的输入流中读取一定数量的字节,并将它们存储到缓冲区数组 b 中. int read(byte[] b, int off, int len) 从包含 ...
- HTML 5 <embed> 标签
定义和用法 <embed> 标签定义嵌入的内容,比如插件. 实例 <embed src="helloworld.swf" />
- Java数据持久层框架 MyBatis之API学习三(XML 映射配置文件)
对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...
- JavaScript的三种类型检测typeof , instanceof , toString比较
1.typeof typeof是js的一个操作符,在类型检测中,几乎没有任何用处. typeof 返回一个表达式的数据类型的字符串,返回结果为javascript中的基本数据类型,包括:number. ...
- Intellij-@Override报错
1.设置 File >> Project Structure >> Project 中设置Project language level如下: 2. 设置 File > ...
- tone() 和 IRremote 冲突的解决办法
tone()函数冲突 http://www.geek-workshop.com/thread-4037-1-1.html 可以自制函数newtone() void newtone(byte toneP ...
- Servlet--超链接,表单提交,重定向,转发4种情况的路径
实际编码中我们经常写路径,写路径既可以写相对路径,也可以写绝对路径.我2年以前我就养成了习惯,只要是写路径我从来都是写绝对路径,因为万一将来我们的项目的目录发生变化,原来要是写相对路径的话就会有路径依 ...