1. C/C++内存分布
《深入理解计算机系统》p580
2. C语言中动态内存管理方式
3. C++中动态内存管理
new/delete操纵内置类型
// 操作内置类型的测试程序
int main()
{
//开辟一个int类型大小的空间
int* p1 = new int;
delete p1;
//开辟一个int类型大小的空间,并初始化为0
int* p2 = new int(0);
delete p2;
//开辟10个int类型大小的空间
int* p3 = new int[10];
delete []p3;
//开辟10个int类型大小的空间,并初始化前6个元素
int* p4 = new int[10]{0,2,3,5,6,1};
delete []p3;
}
注:(1)、调试看看:对于内置类型new操作会不会对变量进行初始化操作?
(2)、调试看看:对于内置类型如果new与delete不匹配会发生什么?
new/delete操纵自定义类型
// 测试new/delete操纵自定义类型的实现机制
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << A() << this <<endl;
}
~A()
{
cout << "~A()" << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = new A;
delete p1;
A* p2 = (A*)malloc(sizeof(A));
free(p2);
A* p3 = new A[10];
delete []p3;
A* p4 = new A[5];
delete p4;
}
注:(1)、调试看看:对于自定义类型new操作会不会对对象进行初始化操作?
答:new对自定义类型会进行初始化操作,并且通过调试发现:实际上new一个自定义类型会执行以下三部操作:(1).第一步new表达式会调用operator new/operator new[]的标准库函数,该函数会分配一块足够大、原始的、未命名的内存空间来存储自定义类型的对象或者对象数组。(2).第二步编译器会运行自定义类型的构造函数,完成对象或者对象数组的初始化。(3).第三步返回一个指向该对象的指针。
(2)、调试看看:对于自定义类型如果new与delete不匹配会发生什么?
答:比如这种情况:new了一块空间,但delete却只释放了一个。对于内置类型,delete/delete[]等同。对于自定义类型这将导致程序崩溃。
(3)、浅谈delete的实现机制(以vs编译器为例)?
答:(1).第一步调用N次对象或者对象数组的析构函数,如果是对象数组(数组内有N个对象),则需要调用N次析构函数,来进行数据清除。(2).第二步调用operator delete/operator delete[ ]来释放内存空间。
所以对于delete p4/delete [ ]p4,前者是释放的是p4指向的空间,后者释放的是p4-4指向的空间。
new/delete与malloc/free的区别
(1).new在申请空间失败时,会抛出异常(异常机制见《C++primer》p172、p684),不需要检查返回值;而malloc在申请空间失败后,会返回空指针(空指针:地址为0的空间),所以malloc需要检查返回值。测试代码如下:
#include<exception>
int main()
{
try
{
while(1)
{
int* p1 = new int[1024*1024*1024];
char* p2 = new char[1024*1024*1024];
// 这里如果是char*的话,由于cout自动识别数据类型,这里空间里面的类型会识别为字符串,
// 字符串的打印结束标志符为\0,但是该空间里面全是随机值,直接打印就会乱码。
if(p1)
{
cout << p1 << endl;
// 应该强转类型为void*
cout << (void*)p2 << endl;
}
else
{
cout << "申请空间失败" << endl;
break;
}
}
}
// exception是一个异常类,调用类中的成员函数what()来获取识别异常的字符串,catch括号里面的是
// 异常申明,后面块中为异常处理代码
catch(exception& e)
{
cout << e.what() << endl;
}
return 0;
}
(2).malloc/realloc/free是一个函数、new/delete是操作符。
(3).malloc/realloc分配好空间后不会对空间进行初始化,new会对空间进行初始化。
(4).申请自定义类型对象时,malloc/free只会开辟空间,不会调用他的构造函数/析构函数进行初始化/清除数据;new/delete会调用构造函数/析构函数。
(5).malloc在申请空间时需要手动计算空间大小(字节),而new则不用,new只需要后面跟一个空间类型或者加上对象的个数。
(6).malloc的返回值是一个void*,并且需要强转;而new不需要,new后跟空间类型。
(7).相同点:malloc与new都是在堆上申请空间,并且需要人为手动释放。
4. operator new与operator delete函数
首先:operator new和operator delete不是new&delete的重载,注意区分。《C++primer》p728。最主要的原因为:如果是重载,那么形参必须要有一个是自定义类型,但是operator new/operator delete的参数没有自定义类型,他是一个size_t。
new的底层是由malloc来实现,由于new的核心机制是开辟空间失败抛异常,而malloc的机制开辟空间失败是返回空指针。所以new在开辟空间失败的处理方式上对malloc进行了封装。这样才符合C++面向对象处理错误的方式。
void* operator new(size_t)
{
void* p;
//为了保证new一定可以申请到空间,在实现过程中,使用了while循环来判断是否成功申请
while((p = malloc(size)) == 0)
{
// malloc开辟失败则抛出bad_alloc类型异常
if(_callnewh(size) == 0)
{
static const std::bad_alloc nomen;
_RAISE(nomen);
}
}
// 开辟成功返回p
return (p);
}
另外:operator new也可以单独使用。
/* operator delete: 该函数最终是通过free来释放空间的 */
/* free的实现 */
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
5. new和delete的实现原理
(1)new的实现原理
调用operator new函数来开辟一块足够大的原始的未命名的内存空间。(不成功抛异常)。
再调用构造函数对对象空间进行初始化。
返回该空间的地址。
(2)delete的实现原理
调用析构函数完成对象的数据清理。
再调用operator delete来释放掉空间。
(3).new[ ]的实现原理
调用operator new[ ]函数来开辟一块足够大的原始的未命名的内存空间。(不成功抛异常)。
再调用N次构造函数对对象空间进行初始化。
返回该空间的地址。
(4).delete[ ]的实现原理
调用N次析构函数完成N个对象的数据清理。
再调用operator delete[ ]函数来释放掉空间。
6. 定位new表达式(placement-new)
定位new表达式是对已经分配好了的空间调用构造函数进行初始化。
另外:构造函数的显式调用将在这里用到,其余地方应该不能,析构函数却能显式调用。
class A
{
public:
A(int a = 0)
:_a(a)
{}
~A()
{}
private:
int _a;
};
int main()
{
A* p1 = (A*)malloc(sizeof(A));
if(p1 == nullptr)
{
exit(-1);
}
// 定位new表达式的写法--对p1指向的空间,显式调用他的构造函数初始化
new(p1)A(0);
// 他的释放如下:可以调用delete或者分步调用
p1->~A();
free(p1);
}
拓展:池化技术(链接池、线程池):当要进行频繁的向堆申请内存空间(malloc也是池化技术的一种),为提高效率,需要定制一个内存池,malloc和new的共同点就是需要排队向堆申请空间,定位new的出现可以从内存池中来申请内存。例子:stl中list容器中的获取一个节点link_type get_node() {return list_node_allocator:: allocate();}这里的list_node_allocator:: allocate()就是STL_list的内存池。如下stl_list源码中:创建一个新节点,就利用了内存池进行申请空间,再使用定位new进行初始化。construct是初始化函数
他的定义如下:这里的初始化就用了定位new。
void construct(pointer p,const _Ty& value)
{
new(static_cast<void*>(p))_Ty(value);// static_cast<void*>强转类型
}
7. 常见-面试题
什么是内存泄露?内存泄露有什么危害?
答:在使用new/malloc()/operator new/realloc开辟空间后没有使用delete/free()/operator delete进行内存释放,导致这块空间失去控制一直不释放,造成空间浪费。
危害:如果存在长时间的内存泄漏,将会导致内存空间被消耗完,致使程序崩溃。