转至:http://www.cnblogs.com/fly1988happy/archive/2012/04/26/2471099.html
1. malloc()函数
1.1 malloc的全称是memory allocation,中文叫动态内存分配。
原型:extern void *malloc(unsigned int num_bytes);
说明:分配长度为num_bytes字节的内存块。如果分配成功则返回指向被分配内存的指针,分配失败返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。
1.2 void *malloc(int size);
说明:malloc 向系统申请分配指定size个字节的内存空间,返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。
备注:void* 表示未确定类型的指针,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者...)
1.3 free
void free(void *FirstByte): 该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。
1.4注意事项
1)申请了内存空间后,必须检查是否分配成功。
2)当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。
3)这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。
4)虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。
1.5 malloc()到底从哪里得到了内存空间?
答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
2. new运算符
2.1 C++中,用new和delete动态创建和释放数组或单个对象。
动态创建对象时,只需指定其数据类型,而不必为该对象命名,new表达式返回指向该新创建对象的指针,我们可以通过指针来访问此对象。
int *pi=new int;
这个new表达式在堆区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针pi 。
2.2 动态创建对象的初始化
动态创建的对象可以用初始化变量的方式初始化。
int *pi=new int(100); //指针pi所指向的对象初始化为100
string *ps=new string(10,’9’);//*ps 为“9999999999”
如果不提供显示初始化,对于类类型,用该类的默认构造函数初始化;而内置类型的对象则无初始化。
也可以对动态创建的对象做值初始化:
int *pi=new int( );//初始化为0
int *pi=new int;//pi 指向一个没有初始化的int
string *ps=new string( );//初始化为空字符串 (对于提供了默认构造函数的类类型,没有必要对其对象进行值初始化)
2.3 撤销动态创建的对象
delete表达式释放指针指向的地址空间。
delete pi ;// 释放单个对象
delete [ ]pi;//释放数组
如果指针指向的不是new分配的内存地址,则使用delete是不合法的。
2.4 在delete之后,重设指针的值
delete p; //执行完该语句后,p变成了不确定的指针,在很多机器上,尽管p值没有明确定义,但仍然存放了它之前所指对象的地址,然后p所指向的内存已经被释放了,所以p不再有效。此时,该指针变成了悬垂指针(悬垂指针指向曾经存放对象的内存,但该对象已经不存在了)。悬垂指针往往导致程序错误,而且很难检测出来。
一旦删除了指针所指的对象,立即将指针置为0,这样就非常清楚的指明指针不再指向任何对象。(零值指针:int *ip=0;)
2.5 区分零值指针和NULL指针
零值指针,是值是0的指针,可以是任何一种指针类型,可以是通用变体类型void*也可以是char*,int*等等。
空指针,其实空指针只是一种编程概念,就如一个容器可能有空和非空两种基本状态,而在非空时可能里面存储了一个数值是0,因此空指针是人为认为的指针不提供任何地址讯息。参考:http://www.cnblogs.com/fly1988happy/archive/2012/04/16/2452021.html
2.6 new分配失败时,返回什么?
1993年前,c++一直要求在内存分配失败时operator new要返回0,现在则是要求operator new抛出std::bad_alloc异常。很多c++程序是在编译器开始支持新规范前写的。c++标准委员会不想放弃那些已有的遵循返回0规范的代码,所以他们提供了另外形式的operator new(以及operator new[])以继续提供返回0功能。这些形式被称为“无抛出”,因为他们没用过一个throw,而是在使用new的入口点采用了nothrow对象:
class widget { ... };
widget *pw1 = new widget;// 分配失败抛出std::bad_alloc
if (pw1 == 0) ... // 这个检查一定失败
widget *pw2 = new (nothrow) widget; // 若分配失败返回0
if (pw2 == 0) ... // 这个检查可能会成功
3. malloc和new的区别
3.1 new 返回指定类型的指针,并且可以自动计算所需要大小。
比如:
1) int *p;
p = new int; //返回类型为int* 类型(整数型指针),分配大小为 sizeof(int);
或:
int* parr;
parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100;
2) 而 malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。
int* p;
p = (int *) malloc (sizeof(int)*128);//分配128个(可根据实际需要替换该数值)整型存储单元,并将这128个连续的整型存储单元的首地址存储到指针变量p中
double *pd=(double *) malloc (sizeof(double)*12);//分配12个double型存储单元,并将首地址存储到指针变量pd中
3.2 malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。
除了分配及最后释放的方法不一样以外,通过malloc或new得到指针,在其它操作上保持一致。
4.有了malloc/free为什么还要new/delete?
1) malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
2) 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。
3) 既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。
5. new与operator new
C++中有很多语法让人难以理解,如:new operator(操作符,下同)和operator new之间差异,确切的说,应该是new与operator new 的区别。
5.1 new operator
如下代码:
string *ps=new string("memory management");
这里所使用的new就是所谓new operator,是由C++语言内建的,就像sizeof那样,不能改变意义,总是做相同的事情。
这个动作的含义分为两方面:
第一,它分配足够的内存,用来放置某类型的对象。对于上例而言,它分配足够放置一个string 对象内存。
第二,它调用一个构造函数,为刚才分配的内存中的那个对象设定初始值。
new operator总是做这两件事,无论如何你是不能改变其行为。
5.2 operator new
能够改变的是用来容纳对象的那块内存的分配行为,new operator调用某个函数,执行必要的内存分配动作,你可以重写或者重载那个函数,改变其行为。这个函数名称就叫operator new 。
函数 operator new 通常声明如下:
void * operator new (size_t size);
其返回类型void*。即返回一个指针,指向一块原始的、未设置初始值的内存。
函数中的size_t参数表示需要分配多少内存,你可以将operator new 重载,加上额外的参数,但第一个参数类型必须总是size_t。
或者你从来没有直接用过operator new ,但是你可以像调用任何其他函数一样地调用它。
void* rawMemory=operator new(sizeof(string));
这里的operator new 将返回指针,它指向一块足够容纳string对象的内存。
和malloc一样,operator new 的唯一任务就是分配内存,它不知道什么是构造函数,它只负责分配内存。
取得operator new 返回的内存并将之转为一个对象,是new operator的责任。
5.3 当编译器看到这个句子:
string *ps=new string("memory management");
它必须产生一些代码,或多或少会反映如下行为:
1) void* memory=operator new(sizeof(string)); //取得原始内存,用于放置一个string对象
2) call string::string("memory management") on *memory;//将内存中对象初始化
3) string *ps=static_cast<string*>(memory); //让ps指向新完成的对象
注意第二步,调用一个构造函数。身为程序员没有权利绕过new operator像这么使用构造函数,但是编译器却是这么干的。
这就是为什么如果你想要做出一个heap-based object,一定要用new operator的原因。
也就是说new 出来的东西都放在heap里面,而无法直接调用“对象初始化所必须的构造函数”。
6. placement new
6.1 有时候你真的会想直接调用一个构造函数,针对一个已经存在的对象调用其构造函数,并无意义,因为构造函数用来对象初始化,而对象只能初始化一次。但是你偶尔会有一些分配好的原始内存,你需要在上面构建对象,有一个特殊的地方 operator new 称为placement new,允许这么做。
例如:
class Widget { public: Widget(int widgetSize); ...... };
Widget* constructWidgetInBuffer(void *buffer,int size) { returnnew (buffer) Widget(size); }
此函数返回指针,指向一个Widget object,它被构造于传递给此函数的一块内存缓存区上。当程序运行到共享内存或者内存I/O映射。这类函数可能是有用的,因为在那样运用中,对象必须置于特定的地址,或者置于特殊函数分配出来的内存上。
6.2 函数内部
Widget* constructWidgetInBuffer 只有一个表达式new (buffer) Widget(size),
有点奇怪,其实不足为奇,这是new operator的用法之一,指定一个额外的自变量(buffer)作为new operator "隐式调用operator new "。于是,被调用的operator new 除了接受"一定要有size_t自变量"之外,还接受了一个void* 参数,指向一块内存,准备用来接受构造好的对象。这样的operator new 就是所谓的placement new :
void * operator new(size_t size,void* location)
{
return location;
}
operator new 的目的是要为对象找到一块内存,然后返回一个指针指向它,在placement new 的情况下,调用者已经知道指向内存的指针了,因为调用者知道对象应该放在哪里。因此placement new 唯一需要做的就是将它获得的指针再返回。
至于没有用到(但一定得有)的size_t参数,之所以不赋予名称,为的是避免"编译器某物未被使用"的警告。
另外注意:placement new 是C++标准程序库的一部分,要使用placement new 得用#include<new>,旧式编译器用 #include<new.h>
回头想想placement new ,我们便能了解new operator和operator new之间的关系。
两个术语表面上令人迷惑,但其实很好理解:
1)如果你希望将对象产生于heap,就是得new operator,它不但分配内存而为该对象调用一个构造函数。
2)如果你只是打算分配内存,请用operator new,就没有构造函数被调用。
3)如果你打算在heap object产生自己决定的内存分配方式,请写一个自己的operator new。并使用new operator,它将会自动调用你所写的operator new。
4)如果你打算在已经分配(并拥有指针)的内存构造对象,请使用placement new 。
7. delete 与内存释放
为了避免resource leaks,每一个动态分配行为都必须匹配一个相应的释放动作。
7.1 函数 operator delete对于内建的delete operator(操作符)就好像 operator new 对于new operator一样。
string *ps;
...
delete ps; //使用delete operator.
内存释放动作是由operator delete执行的,通常声明如下:
void operator delete(void* memoryToBeDeallocated);
因此 delete ps;会造成编译器代码如下:
1)ps->~string();//调用析构函数
2)operator delete(ps);//释放对象所占用的内存
7.2 这里提示我们,如果只打算处理原始的、未设初值的内存,应该完全回避 new operator和delete operator。改为调用operator new取得内存并以operator delete归还系统。
例如:
void* buffer=operator new (50*sizeof(char));//分配内存,放置50个char,没有调用构造函数
...
operator delete(buffer); //释放内存,而没有直接调用析构函数。
这组行为类似malloc和free。
7.3 placement new
如果使用了placement new ,在某块内存中产生对象,你应该避免那块内存使用delete operator(操作符)。
因为delete operator会调用operator delete来释放内存,但是该内存所含的对象最初并不是由operator new 分配来的。placement new只是返回它接收的指针而已,谁知道那个指针从哪里来呢?
所以为了抵消该对象的构造函数的影响,使用placement new 时应该直接调用该对象的析构函数。
例如:
void * mallocShared(size_t size);//申请分配内存
void freeShared(void * momery);//释放内存
void* sharedMemory=mallocShared(sizeof(Widget));
Widget *pw=constructWidgetBuffer(sharedMemory,10);//使用前面Widget类的placement new
...
delete pw;//无定义,因为sharedMemory来自mallocShared,不是来自new。
pw->~Widget();//OK,析构函数pw所指Widget对象,但并未释放Widget所占用内存。
freeShared(pw);//OK,释放pw所指的内存,不调用任何析构函数。
如上述所示,如果交给placement new的原始内存(raw memory)本身是动态分配而得的,那么最终得释放那块内存,以避免memory leak。
8. 动态分配数组(Arrays)
前面所做的都是基于单一对象上的,如果是一组对象呢?
string *ps=new string[10];//分配一个对象数组
8.1 这里的new 与前面的new 行为类似,但略有不同,这里不能再operator new分配内存,而是以operator new[]负责分配。
和operator new 一样,operator new[]也可以被重载。
注:operator new[]是相当晚的时候才加入C++的一个特性,所以你的编译器不一定能支持它。如果是这样,全局的operator new 会被用来为每一个数组分配内存(不论数组中的对象是什么类型)。在这样的编译器下定制“数组内存分配行为”很困难,因为你得改写全局的operator new才行。默认情况下,全局版的operator new 负责程序中所有的动态内存分配,所以其行为的任何改变都可能带来全局的影响。
另外,前面讲过,operator new 只允许size_t一个参数。所以你如果决定声明为自己的函数,你的程序便不兼容于任何做了相同决定的程序库。
多方面考虑之下,如果编译器不支持operator new[],定制数组内存管理行为,不是一个明智的决定。
8.2 数组的new 与单一对象的new所调用的构造函数不同,数组的new 必须针对数组中每一个对象调用一个构造函数。
string *ps=new string[10];//调用operator new[]以分配足够容纳10个string对象的内存,然后针对每个元素调用string的默认构造函数。
同样的,当使用了delete,它也会针对数组中每一个元素调用析构函数,然后再调用operator delete[]释放内存。
如:delete []ps;//为数组中的每一个元素调用string 析构函数,然后再调用 operator delete[] 释放内存。 (先调用析构函数,再释放内存。)
跟operator delete一样 operator delete[]也可以被重载。
最后小结一下,new 和delete都是内建的操作符,语言本身所固定了,无法重新定制。但它所调用的内存分配/释放的函数,即operator new和operator delete可以被重载。