malloc()函数(memory allocation)
分配指定长度的内存块。如果分配成功则返回指向被分配内存的指针,分配失败返回NULL。当内存不再使用时,应使用free()函数将内存块释放,防止内存泄漏。返回类型是void*类型。void*表示未确定类型的指针,即申请内存空间时还不知道存储什么类型的数据。C,C++规定,void*类型可以强制转换为任何其它类型的指针。
申请了内存空间后,必须检查是否分配成功。使用完毕需要释放;释放后应该把指向这块内存的指针置成NULL,防止程序再次使用该指针。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。
malloc是从堆上获得内存空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
new运算符
C++中,用new和delete动态创建和释放数组或单个对象。动态创建的对象可以用初始化变量的方式初始化。
int *pi=newint(100); //指针pi所指向的对象初始化为100
如果不提供显示初始化,对于类类型,用该类的默认构造函数初始化;而内置类型的对象则无初始化。
new分配失败时,返回什么?
1993年前,c++一直要求在内存分配失败时operator new要返回0,现在则是要求operator new抛出std::bad_alloc异常。很多c++程序是在编译器开始支持新规范前写的。c++标准委员会不想放弃那些已有的遵循返回0规范的代码,所以他们提供了另外形式的operator new(以及operator new[])以继续提供返回0功能。这些形式被称为“无抛出”,因为他们没用过一个throw,而是在使用new的入口点采用了nothrow对象:
widget *pw1 = new widget;// 分配失败抛出std::bad_alloc
if (pw1 == 0) // 这个检查一定失败
widget *pw2 = new(nothrow) widget; // 若分配失败返回0
if (pw2 == 0) // 这个检查可能会成功
malloc和new的区别
malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。
new 返回指定类型的指针,并且可以自动计算所需要大小。而malloc则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。
malloc只管分配内存,并不能对所得的内存进行初始化。
有了malloc/free为什么还要new/delete?
对于非内置数据类型而言,只用maloc/free无法满足要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。malloc/free不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
操作系统有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。
new与malloc的区别
1. 申请的内存所在位置
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。
那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。
特别的,new甚至可以不为对象分配内存!定位new的功能可以办到这一点:
new(place_address) type
place_address为一个指针,代表一块内存的地址。当使用上面这种仅以一个地址调用new操作符时,new操作符调用特殊的operator new,也就是下面这个版本:
void * operatornew (size_t,void *) //不允许重定义这个版本的operator new
这个operator new不分配任何的内存,它只是简单地返回指针实参,然后右new表达式负责在place_address指定的地址进行对象的初始化工作。
2.返回类型安全性
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图方法自己没被授权的内存区域。关于C++的类型安全性可说的又有很多了。
3.内存分配失败时的返回值
new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。在使用C语言时,我们习惯在malloc分配内存后判断分配是否成功:
int *a = (int*)malloc(sizeof(int));
if (NULL == a)
{
...
}
从C语言走入C++阵营的新手可能会把这个习惯带入C++:
int *a = new int();
if (NULL == a)
{
...
}
实际上这样做一点意义也没有,因为new根本不会返回NULL,而且程序能够执行到if语句已经说明内存分配成功了,如果失败早就抛异常了。正确的做法应该是使用异常机制:
try
{
int *a = new int();
}
catch (bad_alloc)
{
...
}
4.是否需要指定内存大小
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。
使用new操作符来分配对象内存时会经历三个步骤:
- 第一步:调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
- 第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。
- 第三部:对象构造完成后,返回一个指向该对象的指针。
使用delete操作符来释放对象内存时会经历两个步骤:
- 第一步:调用对象的析构函数。
- 第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。
总之来说,new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构。而malloc则不会。
6.对数组的处理
C++提供了new[]与delete[]来专门处理数组类型:new对数组的支持体现在它会分别调用构造函数函数初始化每一个数组元素,释放对象时为每个对象调用析构函数。注意delete[]要与new[]配套使用,不然会造成数组对象部分释放的现象,造成内存泄漏。
至于malloc,它并不知道你在这块内存上要放的数组还是啥别的东西,反正它就给你一块原始的内存。所以如果要动态分配一个数组的内存,还需要我们手动自定数组的大小。
operator new/operator delete的实现可以基于malloc,而malloc的实现不可以去调用new。
8.是否可以被重载
opeartor new/operator delete可以被重载。而malloc/free并不允许重载。
9. 能够直观地重新分配内存
使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。
new没有这样直观的配套设施来扩充内存。
总结:
特征 | new/delete | malloc/free |
分配内存的位置 | 自由存储区 | 堆 |
成功返回 | 指定类型指针 | void* |
内存分配失败返回值 | 默认抛出异常 | 返回NULL |
分配内存的大小 | 由编译器根据类型计算得出 | 必须显式指定字节数 |
处理数组 | 有处理数组的new[] | 需要用户计算数组的大小后进行内存分配 |
已分配内存的扩充 | 无法处理 | 使用realloc简单完成 |
是否相互调用 | 可以,看具体的operator new/delete实现 | 不可调用new |
函数重载 | 允许 | 不允许 |
构造函数与析构函数 | 调用 | 不调用 |