【转】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.//平台: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 ;
}
A* a = new A;
push ;不含数据成员的类占用一字节空间,此处压入sizeof(A)
call operator new (13013C2h) ;调用operator new(size_t size)
mov esi,eax ;返回值保存到esi
0130102B add esp, ;平衡栈
0130102E mov dword ptr [esp+],esi ;
mov dword ptr [esp+14h],
0130103A test esi,esi ;在operator new之后,检查其返回值,如果为空(分配失败),则不调用A()构造函数
0130103C je wmain+62h (1301062h) ;为空 跳过构造函数部分
0130103E mov eax,dword ptr [__imp_std::endl (1302038h)] ;构造函数内部,输出字符串
mov ecx,dword ptr [__imp_std::cout (1302050h)]
push eax
0130104A push offset string "call A constructor" (1302134h)
0130104F push ecx
call std::operator<<<std::char_traits<char> > (13011F0h)
add esp,
mov ecx,eax
0130105A call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1302040h)]
jmp wmain+64h (1301064h) ;构造完成,跳过下一句
xor esi,esi ;将esi置空,这里的esi即为new A的返回值
mov dword ptr [esp+14h],0FFFFFFFFh
delete a;
0130106C test esi,esi ;检查a是否为空
0130106E je wmain+9Bh (130109Bh) ;如果为空,跳过析构函数和operator delete
mov edx,dword ptr [__imp_std::endl (1302038h)] ;析构函数 输出字符串
mov eax,dword ptr [__imp_std::cout (1302050h)]
0130107B push edx
0130107C push offset string "call A destructor" (1302148h)
push eax
call std::operator<<<std::char_traits<char> > (13011F0h)
add esp,
0130108A mov ecx,eax
0130108C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1302040h)]
push esi ;压入a
call operator delete (13013BCh) ;调用operator delete
add esp,
通过反汇编可以看出A* = new A包含了operator new(sizeof(A))和A()两个步骤(当然,最后还要将值返回到a)
delete a包含了~A()和operator delete(a)两个步骤。
二 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(); |
#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
#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 ;
}

A* a = (A*)::operator new(sizeof(A)); //分配
00F9151D push
00F9151F call operator new (0F91208h) ;调用::operator new(size_t size)也就是throwing()版本
00F91524 add esp,
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 ;压入p
00F91530 call operator new (0F91280h);调用operator new(size_t, void* p)即placement()版本 只是简单返回p
00F91535 add esp,
00F91538 mov dword ptr [ebp-0E0h],eax ;将p放入[ebp-0E0h]
00F9153E mov dword ptr [ebp-],
00F91545 cmp dword ptr [ebp-0E0h], ;判断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], ;将[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-],0FFFFFFFFh a->~A(); //析构
00F9157E push
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,
A* a = (A*)::operator new(sizeof(A)); //分配
010614FE push
call operator new (1061208h)
add esp,
mov dword ptr [a],eax //new(a) A(); //构造
a->A::A();
0106150B mov ecx,dword ptr [a]
0106150E call operator new (1061285h) a->~A(); //析构
push
mov ecx,dword ptr [a]
call A::`scalar deleting destructor' (1061041h) ::operator delete(a); //释放
0106151D mov eax,dword ptr [a]
push eax
call operator delete (10610B9h)
add esp,
三 operator new重载:
1.在类中重载operator new
#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 ;
}

如果类A中没有对operator new的重载,那么new A和new(std::nothrow) A;都将会使用全局operator new(size_t size)。可将A中两个operator new注释掉,并且在A外添加一个全局operator new重载:
void* ::operator new(size_t size)
{
std::cout<<"call global operator new"<<std::endl;
return malloc(size);
}
程序输出:

error C2660: “A::operator new”: 函数不接受 2 个参数。
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);
}
2.重载全局operator new
3.类中operator new和全局operator new的调用时机
四 operator new运用技巧和一些实例探索
1.operator new重载运用于调试:
//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 ;
}
输出:

2.内存池优化
3.STL中的new
五 delete的使用
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);
}
六 关于new和内存分配的其他
1.set_new_handler
#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(); //使new_handler有效
set_new_handler(nomem_handler);//指定入口函数 函数原型void f();
std::cout<<"try to alloc 2GB memory...."<<std::endl;
char* a = (char*)malloc(***);
if(a)
std::cout<<"ok...I got it"<<std::endl;
free(a);
system("pause");
}
2.new分配数组
//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);
}
};
//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[];
delete []p1; system("pause");
return ;
}
输出:

参考文献:
【转】C++ 内存分配(new,operator new)详解的更多相关文章
- C++ 内存分配(new,operator new)详解
参考:C++ 内存分配(new,operator new)详解 如何限制对象只能建立在堆上或者栈上 new运算符和operator new() new:指我们在C++里通常用到的运算符,比如A* a ...
- [转载]windows任务管理器中的工作设置内存,内存专用工作集,提交大小详解
windows任务管理器中的工作设置内存,内存专用工作集,提交大小详解 http://shashanzhao.com/archives/832.html 虽然是中文字,但是理解起来还是很困难,什么叫工 ...
- c++中内存拷贝函数(C++ memcpy)详解
原型:void*memcpy(void*dest, const void*src,unsigned int count); 功能:由src所指内存区域复制count个字节到dest所指内存区域. 说明 ...
- windows任务管理器中的工作设置内存,内存专用工作集,提交大小详解
虽然是中文字,但是理解起来还是很困难,什么叫工作设置内存,什么叫内存专用工作集,什么叫提交大小,区别是什么,让人看了一头雾水. 通俗的讲工作设置内存是程序占用的物理内存(包含与其他程序共享的一部分), ...
- Android内存优化(三)详解内存分析工具MAT
前言 在这个系列的前四篇文章中,我分别介绍了DVM.ART.内存泄漏和内存检测工具的相关知识点,这一篇我们通过一个小例子,来学习如何使用内存分析工具MAT. 1.概述 在进行内存分析时,我们可以使用M ...
- tomcat内存配置及配置参数详解
1.jvm内存管理机制: 1)堆(Heap)和非堆(Non-heap)内存 按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配.堆是在 Java 虚拟 ...
- Bitmap之内存缓存和磁盘缓存详解
原文首发于微信公众号:躬行之(jzman-blog) Android 中缓存的使用比较普遍,使用相应的缓存策略可以减少流量的消耗,也可以在一定程度上提高应用的性能,如加载网络图片的情况,不应该每次都从 ...
- c++内存中字节对齐问题详解
一.什么是字节对齐,为什么要对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址 ...
- 内存管理-MRC与ARC详解
Objective-C提供了两种内存管理机制MRC(Mannul Reference Counting)和ARC(Automatic Reference Counting),为Objective-C提 ...
随机推荐
- POJ3468 A Simple Problem With Integers 树状数组 区间更新区间询问
今天学了很多关于树状数组的技巧.一个是利用树状数组可以简单的实现段更新,点询问(二维的段更新点询问也可以),每次修改只需要修改2个角或者4个角就可以了,另外一个技巧就是这题,原本用线段树做,现在可以用 ...
- java数据结构学习(一)之二分查找
二分查找法与我们在孩童时期玩过的猜数的游戏一样,这个游戏里一个朋友会让你猜他正想的一个1至100的数,当你猜了一个数后,他会告诉你三种选择的一个:你猜的比她想的大,或小,或猜中了.为了能用最少的次 ...
- 2013 ACM-ICPC长沙赛区全国邀请赛——A So Easy!
这题在比赛的时候不知道怎么做,后来看了别人的解题报告,才知道公式sn=(a+sqrt(b))^n+(a-sqrt(b))^n; 具体推导 #include<iostream> #inclu ...
- LR_问题_无法使用LR的Controller,提示缺少license
问题描述 无法使用LR的Controller,提示缺少license 问题解决 使用开始->所有程序->HP LoadRunner->loadrunner,在打开界面的左上角选择co ...
- PowerDesinger逆向数据库物理模型及关系图
原文:PowerDesinger逆向数据库物理模型及关系图 利用PowerDesinger生成的数据库物理模型及关系图 收集五年的开发资料下载地址: http://pan.baidu.com/sha ...
- Android 拦截短信
public class SMSMess extends BroadcastReceiver { @Override public void onReceive(Context arg0, Inten ...
- 【Spring】Redis的两个典型应用场景--good
原创 BOOT Redis简介 Redis是目前业界使用最广泛的内存数据存储.相比memcached,Redis支持更丰富的数据结构,例如hashes, lists, sets等,同时支持数据持久化. ...
- 如何写科技文章的讨论discussion部分
众所周知,讨论部分是在结合自己的研究结果基础上,对整个文章的结论的提炼和升华.这一部分是整个论文的精,往往点睛作用. 同时,很多杂志要求结果和讨论分开,这也就更突出了写好讨论的重要性. 那么,我们应该 ...
- linux下文件夹的创建、复制、剪切、重命名、清空和删除命令
在home目录下有wwwroot目录,wwwroot下有sinozzz目录,即/home/wwwroot/sinozzz 一.目录创建 在/home/wwwroot目录下新建一个sinozzz123的 ...
- 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序(老罗学习笔记2)
在前一篇文章中,我们介绍了如何在Ubuntu上为Android系统编写Linux内核驱动程序.在这个名为hello的Linux内核驱动程序中,创建三个不同的文件节点来供用户空间访问,分别是传统的设备文 ...