C语⾔中指针动态内存的申请和释放
什么是动态内存的申请和释放?
当程序运⾏到需要⼀个动态变量时,需要向系统的堆中申请⼀块内存,⽤来存储这块变量,这就是内存的申请。当程序不需要这个变量时,就需要把申请的这块内存释放掉,这就是内存的释放。往往内存的申请和释放是⼀起使⽤的,只要有内存的申请,就要有内存的释放,避免出现内存泄漏。
C语⾔指针的申请:
(1).在C语⾔中使⽤malloc函数申请内存,函数原形: void* malloc(size_t size);
参数size代表申请的内存的字节数,参数size_t代表指针的类型,⽐如int char或者是结构体的类型。如果内 存申请成功返回内存的⾸地址,如果申请失败,返回NULL(注意NULL全部⼤写)
molloc函数(2).在使⽤该函数的过程中,需要注意以下⼏点:
1.该函数的参数很简单,只是申请内存的⼤⼩,单位是字节
2.申请内存后,必须要检查是否内存分配成功。
3.申请到的内存是⼀块连续的内存,该函数和free()函数配对使⽤。
4.虽然该函数的类型是(void*),但是在使⽤时还是建议进⾏强制类型转换
(3).内存申请的例⼦:
char *P = NULL;(给指针初始化,防⽌出现野指针)
P = (char *)malloc(10 * sizeof(char));
if(P == NULL){
exit(1);
}else{
gets(P);
}
C语⾔指针的释放:
(1).在C语⾔中使⽤free函数释放内存,函数原形:void free(void *ptr);
参数ptr代表内存的⾸地址,该函数没有返回值。
(2).在使⽤该函数的过程中,需要注意以下⼏点:
1.该函数释放的是指针指向的内存,释放的是内存,不是指针。释放内存后指针要指向NULL
2.和malloc函数配对使⽤,内存申请后要记得释放,避免内存泄漏。
3.不允许重复释放。
(3).内存释放的例⼦:
if(P != NULL){
free(P);
P = NULL; //指针释放之后并不为空,要设置其为空。
}
注意:
1.free()函数只能释放堆空间,堆是⼤家共有的空间,分局部堆和全局堆,全局堆是没有分配的空间,局部堆是⽤户分配的空间。⽤过堆的内存之后要记得还给系统,不然容易发⽣内存泄漏。
2.栈是线程独有的,每个线程的栈都是互相独⽴的,每个函数都有⾃⼰的栈,栈被⽤来在函数之间传递参数。像代码区,静态变量,全局变量,栈区上的变量都是不需要程序员进⾏释放的。(这些区域上的空间,不能⽤free()函数释放)
3.野指针:野指针指的是指向“垃圾”内存的指针,不是NULL指针。出现野指针主要有以下⼏个原因:
①.指针没有初始化(指针如果没有初始化,值是不确定的。也就是说,没有初始化的指针,指向的是垃圾内存,⾮常危险。)
②.指针free之后,没有设置为NULL。
③.指针超越了变量的作⽤范围。指针操作时由于逻辑上的疏忽,导致指针访问了⾮法内存,这种情况是⾮常容易出现的。
④.不要返回指向栈内存的指针。
1. 指针
  1.1 指针的含义:
  简单来说,指针是存储内存地址的变量。当我们声明了⼀个指针变量后,使⽤该指针时系统会根据指针内的地址索引内存块,读取内存内的值。指针因为是⽤来存地址的,所以⼀般固定长度为4个字节。void指针指向内存块的指针。
  指针的定义⽰例:
int a=0;
int *Pointer_a=&a;
  在编写程序时我们可以使⽤&(引⽤运算符或地址运算符)获取变量或常量的地址,例如上例中获取a变量地址就是⽤&a。对于指针来说指针本⾝存储地址,加上*(解除引⽤运算符)读取该地址下的值。如上例,Pointer_a存放的就是a的地址,*Pointer_a就是读取a的值。
  1.2 指针的运算(++和--):
  和数组具有相似之处,指针也可以进⾏地址运算。
  我们可以⽤指针遍历数组试试:
int Array[5]={1,2,3,4,5};
int *p=Array;//Array存放的为数组的第⼀个元素的地址
for(int i=0;i<5;i++)
{
cout<<"Array["<<i+1<<"]: "<<*p<<endl;
p++;
}
2.动态内存分配
  2.1使⽤new和delete动态分配和释放内存:
  new可以申请分配⼀个内存块(申请不⼀定会成功受限于系统的状态),如果成功则返回指向⼀个指针,指向分配的内存,否则会出现异常。delete⽤于释放new所分配的空间,当我们使⽤new分配的空间不在使⽤时,⼀定要及时释放否则会拖慢系统。
  动态内存分配⽰例:
int* p, * p_copy, number = 0;
cout << "请输⼊需要存放的整数数量:";
cin >> number;
p = new int[number];//根据需要动态分配内存空间
p_copy = p;//存储p的初始地址
cout << "请输⼊各整数(⽤空格隔开):";
for (int i = 0; i < number; i++)//输⼊各整数
{
  cin >> *p_copy;
  p_copy++;
}
for (int i = 0; i < number; i++)//输出动态分配的空间内的各整数值
{
  cout <<"第"<<i+1<<"整数为:"<<*p<<endl;
  p++;
}
  2.2动态分配内存空间的注意事项:
  使⽤new分配内存空间后没有释放,导致程序运⾏时间越长系统越慢。要注意在new和delete的配合使⽤,否则会导致内存泄漏问题。在我们对原本有效的指针使⽤delete后指针便变为⽆效指针,此时指针为悬浮指针。还有就是指针在被定义之后没有指向内存空间或存储变量地址,这样的指针也是⽆效的。对⽆效指针解除引⽤程序往往会出现异常。所以,我们可以把指针初始化为NULL,使⽤前对指针是否有效进⾏检查。
  2.3动态分配内存空间异常处理:
  如果在使⽤new时分配不成功,将使得程序中断,并弹出错误窗⼝。对此我们可以编写异常处理程序,在分配成功时正常执⾏,不成功时也能妥善退出。
  例如:
try
{
int *p=new int [536870911];
delete [] p;
}
catch (bad_alloc)
{
cout<<"内存分配失败,程序结束"<<endl;
}
  或者使⽤new(nothrow),在分配失败时返回NULL。
  例如:
int *p=new(nothrow)int [0x1fffffff];
if(p)//检查p是否为空
{
delete [] p;//当p为空时,释放p的内存
}
else
cout<<"内存分配失败,程序退出"<<endl;
1.堆内存分配 :
C/C++定义了4个内存区间:
代码区,全局变量与静态变量区,局部变量区即栈区,动态存储区,即堆(heap)区或⾃由存储区(free store)。
堆的概念:
通常定义变量(或对象),编译器在编译时都可以根据该变量(或对象)的类型知道所需内存空间的⼤⼩,从⽽系统在适当的时候为他们分配确定的存储空间。这种内存分配称为静态存储分配;
有些操作对象只在程序运⾏时才能确定,这样编译时就⽆法为他们预定存储空间,只能在程序运⾏时,系统根据运⾏时的要求进⾏内存分配,这种⽅法称为动态存储分配。所有动态存储分配都在堆区中进⾏。
当程序运⾏到需要⼀个动态分配的变量或对象时,必须向系统申请取得堆中的⼀块所需⼤⼩的存贮空间,⽤于存贮该变量或对象。当不再使⽤该变量或对象时,也就是它的⽣命结束时,要显式释放它所占⽤的存贮空间,这样系统就能对该堆空间进⾏再次分配,做到重复使⽤有限的资源。
2.堆内存的分配与释放
堆空间申请、释放的⽅法:
在C++中,申请和释放堆中分配的存贮空间,分别使⽤new和delete的两个运算符来完成:
指针变量名=new 类型名(初始化式);
delete 指针名;
例如:1、 int *pi=new int(0);
它与下列代码序列⼤体等价:
2、int ival=0, *pi=&ival;
区别:pi所指向的变量是由库操作符new()分配的,位于程序的堆区中,并且该对象未命名。 
堆空间申请、释放说明:
⑴.new运算符返回的是⼀个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,⽽且动态创建的对象本⾝没有名字。
⑵.⼀般定义变量和对象时要⽤标识符命名,称命名对象,⽽动态的称⽆名对象(请注意与栈区中的临时对象的区别,两者完全不同:⽣命期不同,操作⽅法不同,临时变量对程序员是透明的)。
⑶.堆区是不会在分配时做⾃动初始化的(包括清零),所以必须⽤初始化式(initializer)来显式初始化。new表达式的操作序列如下:从堆区分配对象,然后⽤括号中的值初始化该对象。
3.堆空间申请、释放演⽰:
⑴.⽤初始化式(initializer)来显式初始化
int *pi=new int(0);
⑵.当pi⽣命周期结束时,必须释放pi所指向的⽬标:
delete pi;
注意这时释放了pi所指的⽬标的内存空间,也就是撤销了该⽬标,称动态内存释放(dynamic memory deallocation),但指针pi本⾝并没有撤销,它⾃⼰仍然存在,该指针所占内存空间并未释放。
下⾯是关于new 操作的说明
⑴.new运算符返回的是⼀个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,⽽动态创建的对象本⾝没有名字。
⑵.⼀般定义变量和对象时要⽤标识符命名,称命名对象,⽽动态的称⽆名对象(请注意与栈区中的临时对象的区别,两者完全不同:⽣命期不同,操作⽅法不同,临时变量对程序员是透明的)。
⑶.堆区是不会在分配时做⾃动初始化的(包括清零),所以必须⽤初始化式(initializer)来显式初始化。new表达式的操作序列如下:从堆区分配对象,然后⽤括号中的值初始化该对象。
4. 在堆中建⽴动态⼀维数组
①申请数组空间:
指针变量名=new 类型名[下标表达式];
注意:“下标表达式”不是常量表达式,即它的值不必在编译时确定,可以在运⾏时确定。
②释放数组空间:
delete [ ]指向该数组的指针变量名;
注意:⽅括号⾮常重要的,如果delete语句中少了⽅括号,因编译器认为该指针是指向数组第⼀个元素的,会产⽣回收不彻底的问题(只回收了第⼀个元素所占空间),加了⽅括号后就转化为指向数组的指针,回收整个数组。delete [ ]的⽅括号中不需要填数组元素数,系统⾃知。即使写了,编译器也忽略。
#include <iostream.h>
#include <string.h>
void main(){
int n;
char *pc;
cout<<"请输⼊动态数组的元素个数"<<endl;
cin>>n; //n在运⾏时确定,可输⼊17
pc=new char[n]; //申请17个字符(可装8个汉字和⼀个结束符)的内存空间
strcpy(pc,“堆内存的动态分配”);//
cout<<pc<<endl;
delete []pc;//释放pc所指向的n个字符的内存空间
return ; }
5. 动态⼀维数组的说明
① 变量n在编译时没有确定的值,⽽是在运⾏中输⼊,按运⾏时所需分配堆空间,这⼀点是动态分配的优点,可克服数组“⼤开⼩⽤”的弊端,在表、排序与查中的算法,若⽤动态数组,通⽤性更佳。⼀定注意:delete []pc是将n个字符的空间释放,⽽⽤delete pc则只释放了⼀个字符的空间;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。