C&C++动态内存管理
动态内存管理
前言
首先我们要明白数据的内存分配原则
来看几道题
这些答案是怎么来的呢,博主做了下面的总结
分配方式:
[1]从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。静态分配的区域的生命期是整个软件运行期,就是说从软件运行开始到软件终止退出。只有软件终止运行后,这块内存才会被系统回收
[2]在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。在栈中分配的空间的生命期与这个变量所在的函数和类相关。如果是函数中定义的局部变量,那么它的生命期就是函数被调用时,如果函数运行结束,那么这块内存就会被回收。如果是类中的成员变量,则它的生命期与类实例的生命期相同。
[3]从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。
动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,
4大存储区
1.栈区(stack) --编译器自动分配释放,主要存放函数的参数值,局部变量值等;
2.堆区(heap) --由程序员分配释放;
3.全局区或静态区 --存放全局变量和静态变量;程序结束时由系统释放,分为全局初始化区和全局未初始化区;
4.程序代码区–存放函数体的二进制代
有了上面的基础来展开我们的分析
1.c语言中的动态内存管理
因为我们现在已经对c语言中的动态内存管理已经十分熟悉了,所以也就不再过多的介绍基础的知识。
但是我们得清楚以下4个函数是干嘛的:
1.malloc从堆区开辟一块空间,参数为字节数,返回一个空指针(所以必须强制类型转换)
2.calloc与malloc差不多,只是将开辟出的空间初始化。
int *p = calloc(10, sizeof(int));//初始化这10个字节
3.realloc调整malloc开辟出的空间,第二个参数是要调整后的大小
ptrr = realloc(ptr, 1000);//注意开辟失败ptr可能为空
4.free函数。切记这个函数必须与malloc成对使用不然会造成内存泄漏(后面介绍内存泄漏的危害)
1.1
常见的动态内存的错误
1.对空指针解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
2.对开辟的空间越界访问
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}
3.对非动态开辟进行free
void test()
{
int a = 10;
int* p = &a;
free(p);
}
4.释放动态开辟空间的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
5.对一块空间多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
6.忘记释放
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
以上呢就是动态内存中的6大致命错误。
1.2经典笔试题
void GetMemory(char *p)第一题
{
p = (char *)malloc(100);//程序奔溃,str并没有开辟出空间
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
char* GetMemory(void)第二题
{
char p[] = "hello world";
return p;
}
int main()//返回局部变量或临时变量的地址
{
char *str = NULL;
str = GetMemory();
printf(str);
system("pause");
return 0;
}
void GetMemory(char **p, int num)第三题
{
*p = (char *)malloc(num);
}
int main()//将指针的地址传过去,所以程序正常
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
system("pause");
return 0;
}
void Test(void)第四题
{
char *str = (char *)malloc(100);//访问非法空间,但输出world
strcpy(str, “hello”);
free(str);
if (str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
2.c++中的动态内存管理
2.1new/delete操作内置类型
new和delete并不是函数,并且他们操作自定义类型调用构造和析构函数
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[3];
delete ptr4;//释放
delete ptr5;
delete[] ptr6;
}
2.2new/delete实现原理
2.2.1 内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和
释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,
malloc会返回NULL。
2.2.2 自定义类型
new的原理
调用operator new函数申请空间
在申请的空间上执行构造函数,完成对象的构造
delete的原理
在空间上执行析构函数,完成对象中资源的清理工作
调用operator delete函数释放对象的空间
new T[N]的原理
调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申
请
在申请的空间上执行N次构造函数
delete[]的原理
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
2.3 operator new与operator delete函数
实际上并不是new和delete的重载,new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)//源码
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
为什么用operator new来调用malloc而不直接用malloc是因为malloc如果开辟失败返回0,但是我们需要直接抛出异常,所以用operator new将他封装起来就可以完成。
2.3内存泄漏
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上
的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的***控制***,因而造成了内存的浪
费。
2.3.1内存泄漏分类
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,
用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那
么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统
资源的浪费,严重可导致系统效能减少,系统执行不稳定。
总结
常见面试题
malloc/free和new/delete的共同点是:
都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
malloc/free和new/delete的区别是:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间
后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理 - new/delete比malloc和free的效率稍微低点,因为new/delete的底层封装了malloc/free