动态内存分配 (详解版)

malloc和free

C++语言允许使用C语言标准库函数中mallocfree申请和释放内存,保留这两个函数主要有以下3点考虑:

  1. C++程序经常要调用写好的C函数,而在C语言中,只能使用mallocfree
  2. 如果C++程序要允许在C语言环境下,必须使用mallocfree
  3. newdelete的功能是通过调用mallocfree来实现的
  4. newdelete是C++运算符,newdelete是C标准库函数。

C函数库提供了mallocfree两个函数,分别用于执行动态内存分配和释放。它们都在头文件stdlib.h中声明。原型如下:

void *malloc(size_t size);       //无符号整型,unsigned int size
void free(void *pointer);        //pointer是指向所申请内存块的指针。编译器可以完成由其他类型指针向void型指针的转化,因此可直接使用free(指针)就可实现内存释放

下列语句用于申请一段长度为len、数据类型为short的动态内存:

short* p=(short*) malloc (len * sizeof(short)); //由于malloc返回类型是void*,用其返回值对其他类型指针赋值时,必须使用显示转换。
//同时,malloc参数是个无符号整数,其仅仅关心申请字节的大小,并不管申请的内存块中存储的数据类型。
//因此,申请内存的长度须由程序员通过“长度*sizeof(类型)”方式。

malloc的参数就是需要分配的内存字节数。如果内存池中的可用内存可用满足这个需求,malloc就返回一个指向被分配的内存块起始位置的指针。malloc所分配的是一个连续内存,因此实际分配的内存有可能比请求的稍微多一些,这个行为由编译器定义。

如果内存池是空的,或者它的可用内存无法满足请求,malloc函数向操作系统请求,要求得到更多的内存,并在这块新内存上执行分配任务。

如果操作系统无法向malloc提供更多的内存,malloc就返回一个NULL指针。

因此,对每个从malloc返回的指针都进行检查,确保它并非NULL.

free的参数要么是NULL,要么是一个先前从malloc/calloc/realloc返回的值。向free传递一个NULL参数不会产生任何影响。

这些函数维护一个可用内存池,当一个程序另外需要一些内存时,就调用malloc函数,malloc从内存池中提取一块合适的内存,并向该程序返回一个指向这块内存的指针。这块内存此时并没有以任何方式进行初始化。如果对这块内存进行初始化非常重要,要么自己动手初始化,要么使用calloc函数。

当一块以前分配的内存不再使用时,程序调用free函数把它归还给内存池供以后之需。

#include<iostream>
using namespace std;
int main()
{
    int *p=(int*) malloc(sizeof(int)*5); //使用malloc申请一块动态内存
    cout<<"请输入5个整数:"<<endl;
    for(int i=0;i<5;i++)
    {
        cin>>*(p+i);                 //循环输入5个整数,与*p[i]一样
    }
    cout<<"您输入的第3个数是:"<<p[2]<<endl;
    free(p);                        //释放所申请的动态内存
    return 0;
}
new和delete

C++中利用专门的运算符“创建(new)”和“撤销(delete)”对内存进行动态分配,这样便可在程序运行时申请一块未命名的内存用来存储变量或者更为复杂的数据结构,并把该内存的首地址记录下来,以备将来访问。

使用newdelete申请和释放动态内存时,内存 的长度是由编译器自动计算的。

1.使用new动态分配内存

new是一个单目运算符,返回值为指向操作数类型的指针。基本格式如下:

// 类型名 * 指针变量名= new 类型名
int *pNum=new int;

new操作符可根据这个类型名自动计算要分配的存储空间的大小。上述代码会在运行时为一个int型数值分配内存,声明了指向int型的指针pNum,并用动态申请内存的首地址为pNum进行初始化。因此,用指针pNum可以访问这块内存区域。

申请内存的同时可对该区域进行初始化,对基本的变量类型,下列语句是合法的:

//动态申请了大小为int型的内存,将这块区域初始化为8,把该区域的首地址赋值给pNum。
int *pNum=new int(8); 
2.使用delete释放动态申请的内存
delete 指针;

指针指向使用new动态申请的内存块,delete指令会释放动态申请的内存块,但不会删除指针本身,还可以将指针重新指向另一块内存区域。

//delete语句不能释放声明变量获得的内存
int x=3;
int* p=&x;
delete p;    //错误
3.使用new申请动态数组

通过new[]命令动态创建数组,格式如下:

// 类型名 * 指针变量名= new 类型名[元素个数]
int i=5;
int *p=new int[i];  // 合法

上述语句通知编译器动态开辟足以存储“元素个数”个类型为类型名的元素的连续内存空间(数组),并声明“指针变量名”,指向数组的第一个元素。

和通过声明建立数组不同。使用new申请动态数组时,元素个数可以是变量。声明建立数组时,为了避免数组越界,一般都将数组的维数尽量设大一点,因此造成了内存浪费。

注意:new无法对动态申请的数组存储区进行初始化。

对于动态申请的数组,使用完毕后,使用delete[]命令将内存释放,格式如下:

delete[] 指针;

方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。

因此,一定要注意newdelete的配对,new&delete用于为一个实体分配内存,而new[] & delete[]用于为数组分配内存。

#include<iostream>
using namespace std;
int main()
{
    int num=0;                    //定义整型变量代表数组个数
    cout<<"请输入数组个数:"<<endl;
    cin>>num;                     //获取数组个数
    cout<<"请依次输入"<<num<<"个整数(用空格隔开):"<<endl;
    int* p=new int[num];          //申请一块可存放num个int型数据的动态内存,将首地址赋值给指针p;
    for(int i=0;i<num;i++)
    	cin>>p[i];                //for循环结构为动态数组中的元素赋值
    for(i=0;i<num;i++)
        cout<<"第"<<i<<"个数为:"<<p[i]<<endl;  //对数组元素依次输出
    delete [] p;                              //释放动态内存
    return 0; 
}
4.不使用或释放已经释放的内存块

在使用delete释放内存时,delete后的指针并不要求一定是用new命令赋值的那个指针,编译器关心的是内存块的地址,而不是用哪个指针来释放。

#include<iostream>
using namespace std;
int main()
{
    int *p1=new int(8);   //申请一个int型大小的动态内存,初始化为8
    int *p2=p1;           //指针间的赋值
    delete p2;            //释放p2所指的动态内存,实际上已经释放了p1申请的内存;
    //此时对*p1的访问没有意义,因为p1所指向的内存已经被释放,所以程序输出值并不是8,而是一个随机值
    cout<<*p1<<endl; 
    //释放p1所指的动态内存;相当于对已经释放的内存再次释放,此时系统报错,出现不可预料的错误。
    delete p1; 
    return 0; 
}

注意:释放NULL指针(即空指针)是没有问题的。

在使用动态申请的内存块时,应首先判断申请是否成功(指针是否为NULL),代码如下:

char *p=new char[10];
if(p!=null)
    //执行操作
else
    //内存申请失败处理

freedelete一个指针后,该指针所指向的动态内存被释放,但指针的值并不发生变化,常称此时的指针为“野指针”。

通常,在内存释放后,将指针赋值为NULL,这样便不会再次释放已经释放了的内存,也可以通过if(指针!=null)进行防错。

常见的动态内存错误

1)对NULL指针进行解引用操作;

2)对分配的内存进行操作时越过边界;

3)释放并非动态分配的内存可能导致程序立即终止或在晚些时候终止;

4)试图释放一块动态分配的内存的一部分;

5)一块动态内存被释放之后被继续使用。(不要访问已经被free函数释放了的内存)

内存泄漏

当动态分配的内存不再需要时,应该被释放,这样它以后可以被重新分配使用。分配内存但在使用完毕后不释放将引起内存泄漏。即 :内存泄漏是指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统奔溃等严重后果。

在那些所有执行程序共享一个通用内存池的操作系统中,内存泄漏将一点点地榨干可用内存,最终将其一无所有。

野指针

前面提到,指针消亡,并不意味着其所指向的内存会被自动释放,同样,释放动态内存,并不意味着指针会消亡,也不意味着指针的值会改变,如下所示:

//假设p是代码块内声明的局部指针变量,在代码块执行完毕退出时,p自动消亡,但p指向的大小为8、类型为int的内存空间不会释放掉。
//代码退出后,程序无法再通过指针p释放所申请的动态内存,这块内存已经“泄露——————内存泄漏
int *p=new int[8];  
...
//运行以后,指针p非但不会消亡,其值还会保持不变,并不会变为null。此时使用if(指针!=null)进行处理也无法起到防错作用。
delete [] p;        

解决方法:指针被free或者delete后,一定要置为null,没有置为null的指针常称为”野指针“。

下列两种情况也可以称为**”野指针“**:

1)未初始化指针

任何指针变量刚创建时,其内容是随机的,在内存中乱指一通。因此,使用指针前,一定要对其初始化。在声明的同时初始化或赋值,使其指向合法的内存。对于无处可指的指针变量,也要将其赋值/初始化为null,也就是空指针。

2)指向临时变量的指针

当前代码块执行完毕后,代码块中声明的临时变量(包括函数调用时的参数)都自动消亡,其占用的内存空间(栈内存)也被内存管理器收回。

此时,指向这些临时变量的指针便没有意义,使用这些指针会给程序造成不可预料的结果。

内存溢出

内存溢出指:程序在申请内存时,没有足够的内存供申请者使用,或者说,给了一块存储int类型数据的存储空间,但是却存储long类型的数据,就会导致内存不够用,报错,即出现内存溢出的错误。

就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误。

内存溢出原因:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;

3.代码中存在死循环或循环产生过多重复的对象实体;

4.使用的第三方软件中的BUG;

5.启动参数内存值设定的过小

内存溢出的解决方案:
第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加)

第二步,检查错误日志,查看“Out Of Memory”错误前是否有其它异常或错误。

第三步,对代码进行排查和分析,找出可能发生内存溢出的位置。

内存泄漏和内存溢出的联系

1:内存泄漏的堆积最终会导致内存溢出

2:内存溢出:就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误

3:内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。

  • 73
    点赞
  • 415
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值