首页 > 代码库 > C++内存分配new

C++内存分配new

        new表达式在内存生存周期内创建并初始化对象(两阶段:1、调用operator new创建内存,2、调用构造函数构造对象)也就是说,动态创建的对象其生命周期可能超出其创建的作用域。new的语法为:   
   ::(optional) new (placement_params)(optional) (type) initializer(optional)     //  1)
   ::(optional) new (placement_params)(optional) type initializer(optional)         //  2)

  

  1)试图创建一个type类型的对象,这个type可能是数组或者是c++11的auto(自动推导)

  2)和1)类似,但是type不能含有括号
new int(*[10])(); //error,被错误解析为:(new int) (*[10]) ()
new (int (*[10])()); //ok,分配10个函数指针

此外,没有括号的type贪婪匹配:它将匹配一切可能和可以它匹配的符号
new int + 1; // OK,解析为:(new int) + 1,new返回的指针+1
new int * 1; // Error,被错误的解析为:(new int*) (1)
注意:如果type中使用了auto,那么initializer是必选的(理由很简单,要自动推导总不能不给任何提示),auto p = new auto(‘c‘); //创建一个char* p

new表达式试图分配内存并且初始化一个不具名的对象或者一个不具名的数组到该内存中,new表达式返回一个右值指针指向刚构造的对象或者数组(指向数组的起始元素)。注:右值简单的可以理解为可以出现在等号右边的对象,当然c++11中不止这样简单。


如果type是一个数组类型,那么数组的第一维(最低维)必须是std::size_t类型的常量表达式(突然记起C语言中的多维数组最低维必须已知,然后高维个数按照低维展开确定,如:int a[][2]={1,2,3,4}),这是在运行时确定数组大小的唯一方法,称为动态数组。
int n = 42;
double a[n][5]; // Error,直接定义数组时维度不能出现变量
auto p1 = new double[n][5]; // OK,动态数组
auto p2 = new double[5][n]; // Error,最低维必须确定

下面的最低维表达式是错误的:
表达式是非类型,但是在转换为std::size_t前其值是负的
表达式通过用户自定义转换函数后是负的
表达式的值超出了定义的范围
表达式的值小于数组元素个数,如:‘\0‘,(注:某些平台上数组大小声明为0也是可以的)

如果最低维表达式的值是错误的可能出于以下原因(很遗憾,这两点我也不太清楚,所以保留原文内容在后面):
在转换为std::size_t后,最低维仍是在运行时确定的core constant expression。(after conversion to std::size_t, the first dimension is a core constant expression, the program is ill-formed)
new表达式不调用内存分配函数,转而抛出异常std::bad_array_new_length。( the new expression does not call the allocation function, and instead throws an excetion of typestd::bad_array_new_length or derived from it (since C++11).)

最低维可以接受0,并且会调用内存分配函数

注:std::vector提供了一个一维动态数组的new函数


内存分配:
new表达式通过调用合适的内存分配函数来分配内存,如果type是非数组类型则调用operator new()函数,若果type是数组类型,则调用operator new[]()函数(注:operator new和operator delete   和  C语言中的malloc和free相对应,只负责内存的分配和释放)

c++程序提供了全局或者类自定的operator new上面的内存分配函数,如果new表达式前面有::,如::new T或者::new T[n]则使用全局的,这时T中自定义的operator new和operator new[]函数将被忽略。如果没有使用::则首先查找T中的自定义operator new或operator new[]

operator new内存分配函数第一个参数是size_t类型,指定了需要分配的内存大小。当T是非数组类型时这个参数严格等于sizeof(T);当T是数组类型时,这个参数可能大于数组的大小,许多实现都用一个额外的元素保存这个动态数组的大小,这将在operator delete[]调用正确数目的析构函数。
 
此外,如果new表达式用于分配char或unsigned char数组时,需要在调用operator new[]时需要额外的内存保证以后将各对象放入数组中不会超出预设的数组大小

如果提供了placement_parms,它们将作为内存分配函数的额外参数,如下:
new T;      //调用operator new( sizeof(T) )
new T[5];   //调用operator new[]( sizeof(T)*5 + overhead)
new(2,f) T; //调用operator new( sizeof(T), 2, f)

这样的函数称为”placement new”,在已有的内存上构建对象
char* ptr = new char[sizeof(T)]; //分配内存
T* tptr = new(ptr) T; //在已有内存ptr上构造对象T,replacement new
tptr->~T();   //析构
delete[] ptr; //释放内存

构造:

在new表达式中对象的初始化遵从如下规则:

非数组初始化:

      如果没有initializer,对象就默认初始化
      如果initializer后面带有()列表则直接初始化
      如果initializer后面带有{}初始化列表则调用列表初始化(c++11)
数组初始化,那么初始化的次数等于数组大小

      如果没有initializer,则数组每个元素默认初始化

      如果initializer后面带有括号,则数组每个元素采用值初始化

      如果initializer后面带有{},则数组每个元素采用列表初始化(c++11)


如果初始化过程中构造函数抛出异常new表达式调用合适的内存释放函数operator delete()或者operator delete[]()。如果使用的是::new语法则调用全局的operator delete函数,否则优先调用T中自定义的operator delete函数。operator new分配内存得到的指针将作为operator delete的第一个参数


内存泄露:

new表达式创建的动态内存知道显示的调用了delete表达式,如果new表达式的指针被丢失了那么对象将不可获取且内存不能被释放,造成内存泄露,如下:

当在一个作用域中分配内存给指针

int* p = new int(7); // dynamically allocated int with value 7
p = NULL; // memory leak

当指针p走出其作用于后没有释放内存则内存泄露:

void f()
{
    int* p = new int(7);
} // memory leak
或者由于抛出异常导致内存泄露:
void f()
{
   int* p = new int(7);
   g();      //函数g抛出异常
   delete p; //没有异常则释放了内存
} //如果g函数抛出异常则内存泄露

最好的动态内存管理方式是使用智能指针,c++11的智能指针可以让程序员只new而不用管delete,从内存泄露中解脱出来。智能指针关键的是其增加了引用计数,当计数为0时就释放其所指的对象,对应到这里就是计数为0则释放内存。


最后总结下,new/delete,operator new/delete,replacement new:


new的过程:

1)创建内存,调用operator new

2)构造对象,T()


delete的过程:
1)析构 对象,~T()

        2) 释放内存,调用operator delete


其中new []和delete[]类似


operator new和operator和C语言的malloc和free差不多,只负责内存的分配和释放.要实现不同的内存分配行为,应该重载operator new,operator new[]、operator delete、operator delete[]也是可以重载的。使用时可以选择使用全局版本和自定义版本


replacement new是在已有内存上构造对象,为什么会这样?想想这样的好处:
1)内存复用:假设不需要多次申请内存,但是需要多次更改对象这时候复用已经分配好的内存,在分配好的内存上重新初始化对象,这样很高效
2)安全:在已有内存上初始化对象,不会new中可能内存不足等原因,对于一些实时长时间不被打扰的应用程序来说,提前分配好内存然后在此内存上安全运行至关重要
3)速度快:new分配内存时还需要到堆空间去寻找空闲的空间,而replacement new完全在已经分配好内存上构建对象显然速度快


replacement new内存的使用方式:如果想在预分配的内存上创建对象,用缺省的new操作符是行不通的。要解决这个问题,可以用placement new构造。它允许构造一个新对象到预分配的内存上。
placement new使用步骤


在很多情况下,placement new的使用方法和其他普通的new有所不同。它的使用步骤:


  第一步  缓存提前分配,有三种方式:
1.为了保证通过placement new使用的缓存区的memory alignment(内存队列)正确准备,使用普通的new来分配它:在堆上进行分配
        class Task ;
        char * buff = new [sizeof(Task)]; //分配内存
(请注意auto或者static内存并非都正确地为每一个对象类型排列,所以,你将不能以placement new使用它们。)
2.在栈上进行分配
        class Task ;
        char buf[N*sizeof(Task)]; //分配内存	
3.还有一种方式,就是直接通过地址来使用。(必须是有意义的地址)
	void* buf = reinterpret_cast<void*> (0xF00F);

第二步:对象的分配
在刚才已分配的缓存区调用placement new来构造一个对象。
Task *ptask = new (buf) Task

第三步:使用
按照普通方式使用分配的对象:
ptask->memberfunction();
ptask-> member;
//...

第四步:对象的析构
一旦你使用完这个对象,你必须调用它的析构函数来毁灭它。按照下面的方式调用析构函数:
ptask->~Task(); //调用外在的析构函数

第五步:释放,delete


一个例子如下:
#include<iostream>
using namespace std;
class test{
    public:
        test(){cout<<"constructor"<<endl;}
        ~test(){cout<<"destructor"<<endl;}
        void show(){cout<<"member fun "<<endl;}
};
int main(){
    char* buf=new char[sizeof(test)];
    test* ptr=new(buf)test;
    ptr->show();
    ptr->~test();
    delete[] buf;
    return 0;
}

程序输出:

constructor
member fun 
destructor