首先我们你知道C语言中使用malloc、calloc、realloc和free进行动态内存管理。
malloc、calloc、realloc用来在堆上开辟空间, free将申 请的空间释放掉。
在C++中这些都还是可以用的,但是我们用到更多的还是new和delete,new[]和delete[];
注意在这里两个组合一定要匹配使用。
一、new、delete.
1.new
首先我们随便写一个代码
int* p=new int
然后我们看他的反汇编
分析它先给我们压栈4byte的参数后再调用operator new。源代码如下:
注意:这里的operator new和operator函数不同(比如operator =),这函数没有重载.
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
_THROW_NCEE(_XSTD bad_alloc, );
}
return (p);
}
分析operator new源代码:如果malloc调用失败,也就是内存申请失败就会调用_callnewh()这个函数
这个callnewh()是什么呢?它是一个new handler,通俗来讲就是new失败的时候调用的回调函数。
namespace std {
#ifdef _M_CEE_PURE
typedef void (__clrcall * new_handler) ();
#else /* _M_CEE_PURE */
typedef void (__cdecl * new_handler) ();
#endif /* _M_CEE_PURE */
#ifdef _M_CEE
typedef void (__clrcall * _new_handler_m) ();
#endif /* _M_CEE */
_CRTIMP2 new_handler __cdecl set_new_handler(_In_opt_ new_handler _NewHandler) throw();
};
这里:
1、调用一个客户指定的错误处理函数——一个new-handler。
2、抛出异常bad_alloc(【旧】返回一个null指针)。
可以通过_set_new_handler来设置。
客户定义的错误处理函数new handler应该什么样子?
void My_NewHandler()//没有参数也不返回任何值的函数
{
std::cerr << "Unable to satisfy request for memory\n";//提示
//1让更多内存可用——程序开始执行就分配一块内存,当new-handler第一次被调用,将它们释还给程序使用。
//2调用std::set_new_handler呼叫另一个new_handler函数。或者直接修改自身,比如改static数据,全局数据,namespace数据。
//3抛出bad_alloc异常(或派生),异常不会被operatornew捕捉,会被传播到内存索求处。
//4不返回,调用abort或者exit终止程序。
std::abort();//终止程序
}
void main()
{
_set_new_handler(My_NewHandler);
while (1)
{
int* p = new int[10000000];
}
}
另外的如果在类中,new还会调用构造函数,而malloc就不会调用构造函数
相同的delete p会调用析构函数,而free不调用.
总结:
{
1.调用operator new分配空间。
调用构造函数初始化对象。
2.new和delete他们只 负责分配空间/释放空间, 不会调用对象构造函数/析构函数来初始化/清理对象。
3.实际operator new和operator delete只是malloc和free的一层封装。
4.new分配失败的时候不像malloc那样返回NULL,它直接抛出异常。要判断是否分配成功应该用异常捕获的机制;
}
2.delete.
先来看反汇编,和new原理几乎相同
调用析构函数清理对象
再调用operator delete释放空间
下面是operator delete实现代码:
void operator delete( void * p )
{
RTCCALLBACK(_RTC_Free_hook, (p, 0));
free( p );
}
RTCCALLBACK默认是空的宏定义,所以这个函数默认情况下就是简单的调用free函数。
总结:
delete简单数据类型默认只是调用free函数。
总结步骤图:
二new[]、delete[]。
1.new[](类的数组)
首先我们可以看反汇编
然后我们F11进去
再F11
这时我们可以看到和new一样最后还是调用的operator new。
再看constructor 这个进去之后是调用构造函数.
仔细看这里的汇编它先给我们push进去一个 2Ch 2Ch是多少呢?是44
我们再来看看这个图
很明显这里的count是编译器给我们申请的字节数,如果你打开监视,你会发现这里的count就是44
这里的ecx就是申请到的空间地址
我们可以看到其中push 0Ah=10;
打开内存窗口可以看到,p地址的上一个地址正好是a也就是10;
这就证明,给ecx+4,也就是ecx指针偏移四个字节,最后将偏移后ecx给p,这里才是用户拿到的地址.
这里就有很多疑惑,我们只需要10*4=40个字节为什么要给我们多申请四个字节呢?
为什么要给申请到的地址先压栈一个10,然后再把偏移四个字节之后地址的给用户使用呢。对类类型,
delete一个数组时(比如,delete []sa;),要为每一个数组元素调用析构函数。但对于delete表达式(比如,这里的delete []p,它并不知道数组的元素个数(只有new函数和delete函数知道)。因此,必须有一种手段来告诉delete表达式的数组大小是多少。那么一种可行的方式就是,多分配一个大小为4字节的空间来记录数组大小,并可以约定前四字节来记录大小。那么,由new函数分配的地址与new表达式返回的地址应该相差4个字节(这可以写程序来验证)。对于非类类型数组和不需要调用析构函数的类类型数组,这多于的四字节就不需要了。
总结:
调用operator new分配空间。
调用N次构造函数分别初始化每个对象。
多申请四个字节用来存放数组大小
2。delete[]
这里就不分析delete[]了和前面的分析方法差不多可以自己动手看看反汇编。
/*****/
一个进击的学生
/****/