C++ 内存分配(new,operator new)

本文主要讲述C++ new运算符和operator new, placement new之间的种种关联,new的底层实现,以及operator new的重载和一些在内存池,STL中的应用。

一 new运算符和operator new():

     new:指我们在C++里通常用到的运算符,比如A* a = new A;  对于new来说,有new和::new之分,前者位于std
     operator new():指对new的重载形式,它是一个函数,并不是运算符。对于operator new来说,分为全局重载和类重载,全局重载是void* ::operator new(size_t size),在类中重载形式 void* A::operator new(size_t size)。还要注意的是这里的operator new()完成的操作一般只是分配内存,事实上系统默认的全局::operator new(size_t size)也只是调用malloc分配内存,并且返回一个void*指针。而构造函数的调用(如果需要)是在new运算符中完成的

     先简单解释一下new和operator new之间的关系:
     关于这两者的关系,我找到一段比较经典的描述(来自于www.cplusplus.com 见参考文献):
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.
     比如我们写如下代码:
     A* a = new A;
     我们知道这里分为两步:1.分配内存,2.调用A()构造对象。事实上,分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),如果没有重载,就调用::operator new(size_t ),全局new操作符由C++默认提供。因此前面的两步也就是:1.调用operator new 2.调用构造函数。这里再一次提出来是因为后面关于这两步会有一些变形,在关于placement new那里会讲到。
//平台: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;  
}  

 
下面我们跟踪一下A反汇编代码,由于Debug版本反汇编跳转太多,因此此处通过Release版本在A* a = new A;处设断点反汇编:
在Release版本中,构造函数和析构函数都是直接展开的。
 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)两个步骤。  

二 operator new的三种形式:

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();
(1)(2)的区别仅是是否抛出异常,当分配失败时,前者会抛出bad_alloc异常,后者返回null,不会抛出异常。它们都分配一个固定大小的连续内存。
用法示例:
A* a = new A; //调用throwing(1)
A* a = new(std::nothrow) A; //调用nothrow(2)
(3)是placement new,它也是对operator new的一个重载,定义于<new>中,它多接收一个ptr参数,但它只是简单地返回ptr
那么它究竟有什么用呢?事实上,它可以实现在ptr所指地址上构建一个对象(通过调用其构造函数),这在内存池技术上有广泛应用。
它的调用形式为:
new(p) A(); //也可用A(5)等有参构造函数。
前面说到,new运算符都会调用operator new,而这里的operator new(size_t, void*)并没有什么作用,真正起作用的是new运算符的第二个步骤:在p处调用A构造函数。这里的p可以是动态分配的内存,也可以是栈中缓冲,如char buf[100]; new(buf) A();

我们仍然可以通过一个例子来验证:
#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;  
}  


上面的代码将对象的分配,构造,析构和释放分离开来,这也是new和delete运算符两句就能完成的操作。
先直接运行可以看到程序输出:

再分别注释掉new(a) A();和a->~A();两句,可以看到对应的构造和析构函数将不会被调用。

2.内存池优化

    operator new的另一个大用处就是内存池优化,内存池的一个常见策略就是分配一次性分配一块大的内存作为内存池(buffer或pool),然后重复利用该内存块,每次分配都从内存池中取出,释放则将内存块放回内存池。在我们客户端调用的是new运算符,我们可以改写operator new函数,让它从内存池中取出(当内存池不够时,再从系统堆中一次性分配一块大的),至于构造和析构则在取出的内存上进行,然后再重载operator delete,它将内存块放回内存池。关于内存池和operator new在参考文献中有一篇很好的文章。这里就不累述了。

  3.STL中的new

    在SGI STL源码中,defalloc.h和stl_construct.h中提供了最简单的空间配置器(allocator)封装,见《STL源码剖析》P48。它将对象的空间分配和构造分离开来,虽然在defalloc.h中仅仅是对::operator new和::operator delete的一层封装,但是它仍然给STL容器提供了更加灵活的接口。SGI STL真正使用的并不是defalloc.h中的分配器,而是stl_alloc.h中的SGI精心打造的"双层级配置器",它将内存池技术演绎得淋漓尽致,值得细细琢磨。顺便提一下,在stl_alloc.h中并没有使用::operator new/delete 而直接使用malloc和free。具体缘由均可参见《STL源码剖析》。

三 delete的使用

    delete的使用基本和new一致,包括operator delete的重载方式这些都相似,只不过它的参数是void*,返回值为void。但是有一点需要注意,operator delete的自定义参数重载并不能手动调用。

参考:https://i-blog.csdnimg.cn/blog_migrate/f03d1cdabc2c628bc93a396ed10cf457.png
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值