动态内存分配

来源:互联网 发布:田岛美工刀片价格 编辑:程序博客网 时间:2024/06/14 15:41


1.内存区间

C/C++定义了4个内存区间:代码区,静态存储区,局部变量区即栈区(存放自动变量或者函数形参),动态存储区,即堆(heap)区或自由存储区。

静态存储区

外部变量、全局变量、静态变量存放在其中。

栈区

栈是线程独有的,保存其运行状态和局部自动变量、形式参数以及函数的返回地址等。通常定义变量(或对象),编译器在编译时都可以根据该变量(或对象)的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。栈在线程开始的时候初始化,每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。

堆区

有些操作对象只在程序运行时才能确定,这样编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配。所有动态存储分配都在堆区中进行。

当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存储空间,用于存储该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。

2.new 与delete语句的使用

堆内存的分配与释放

在C++中,申请和释放堆中分配的空间,分别使用new和delete的两个运算来完成:    
指针变量名=new 类型名(初始化式);
      delete 指针名;

new 分配一段新的内存空间,然后将该内存空间的地址存入到变量中。” 所以,最终指针变量名中仍然是存储了一个变量的地址,只是,这是一个“无名”变量。

堆空间申请、释放说明

⑴.new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而且动态创建的对象本身没有名字。
⑵.一般定义变量和对象时要用标识符命名,称命名对象,而动态的称无名对象(请注意与栈区中的临时对象的区别,两者完全不同:生命期不同,操作方法不同,临时变量对程序员是透明的)。

⑶.堆区是不会在分配时做自动初始化的(包括清零),所以必须用自定义初始化。new表达式的操作序列如下:从堆区分配对象,然后用括号中的值初始化该对象。

堆空间申请、释放示例

⑴.显式初始化 
int *pi=new int(0);
⑵.当pi生命周期结束时,必须释放pi所指向的目标:
delete pi;

注意这时释放了pi所指的目标的内存空间,也就是撤销了该目标,称动态内存释放(dynamic memory deallocation),但指针pi本身并没有撤销,它自己仍然存在,该指针所占内存空间并未释放。 

在堆中建立动态一维数组

①申请数组空间:
指针变量名=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 ;
}

动态一维数组的说明

1.变量n在编译时没有确定的值,而是在运行中输入,按运行时所需分配堆空间,这一点是动态分配的优点,可克服数组“大开小用”的弊端,在表、排序与查找中的算法,若用动态数组,通用性更佳。一定注意:delete []pc是将n个字符的空间释放,而用delete pc则只释放了一个字符的空间;

2.如果有一个char *pc1,令pc1=p,同样可用delete [] pc1来释放该空间。尽管C++不对数组作边界检查,但在堆空间分配时,对数组分配空间大小是纪录在案的。

3.没有初始化式(initializer),不可对数组初始化。 

3.指针数组和数组指针

指针类型:

int*ptr;//指针所指向的类型是int 

char*ptr;//指针所指向的的类型是char 

int**ptr;//指针所指向的的类型是int* (也就是一个int * 型指针) 

int(*ptr)[3];//指针所指向的的类型是int()[3] //二维指针的声明

指针数组:
一个数组里存放的都是同一个类型的指针,通常我们把他叫做指针数组。 
比如 int * a[2];它里边放了2个int * 型变量 .
int * a[2]; 
a[0]= new int[3]; 
a[1]=new int[3]; 
delete a[0]; 
delete a[1]; 
注意这里 是一个数组,不能delete [] ; 

数组指针
一个指向一维或者多维数组的指针.
int * b=new int[10]; 指向一维数组的指针b ; 
注意,这个时候释放空间一定要delete [] ,否则会造成内存泄露, b 就成为了空悬指针 
int (*b2)[10]=new int[10][10]; 注意,这里的b2指向了一个二维int型数组的首地址. 
注意:在这里,b2等效于二维数组名,但没有指出其边界,即最高维的元素数量,但是它的最低维数的元素数量必须要指定!就像指向字符的指针,即等效一个字符串,不要把指向字符的指针说成指向字符串的指针。
int(*b3) [30] [20]; //三级指针――>指向三维数组的指针; 
int(*b2) [20];     //二级指针;――>指向二维数组的指针; 
b3=new int [1] [20] [30]; 
b2=new int [30] [20]; 
删除这两个动态数组可用下式: 
delete [] b3; //删除(释放)三维数组; 
delete [] b2; //删除(释放)二维数组;

在堆中建立动态多维数组
new 类型名[下标表达式1] [下标表达式2]……;

例如:建立一个动态三维数组
float (*cp)[30][20] ; //指向一个30行20列数组的指针,指向二维数组的指针
cp=new float [15] [30] [20]; //建立由15个30*20数组组成的数组;

注意:cp等效于三维数组名,但没有指出其边界,即最高维的元素数量,就像指向字符的指针即等效一个字符串,不要把指向字符的指针,说成指向字符串的指针。这与数组的嵌套定义相一致。


4. Malloc()和free()的用法

malloc()和free()的基本概念以及基本用法

1、函数原型及说明

void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针(NULL)。

void free(void*FirstByte): 该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它可以被重新分配。

2、函数的用法:
        // Code...
        char *Ptr = NULL;
        Ptr = (char *)malloc(100 * sizeof(char));
        if (NULL == Ptr)
    {
        exit (1);
    }
      ….
        free(Ptr);
        Ptr = NULL;

定义一个指针,在一个函数里申请了一块内存然后通过malloc函数返回传递给这个指针。如果malloc和free在不同的函数中,不要认为调用malloc的函数返回时,函数所在的栈被销毁指针也跟着销毁,申请的内存也就一样跟着销毁了!因为申请的内存在堆上,而函数所在的栈被销毁跟堆完全没有啥关系。此时,释放这块内存这项工作就应该留给其他函数了。

3、关于函数使用需要注意的一些地方:

A、申请了内存空间后,必须检查是否分配成功。

B、当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。

C、这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。因此,释放内存后把指针指向NULL,防止指针在后面不小心又被解引用了。

D、虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。

malloc()以及free()的机制

操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的malloc申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
free:释放ptr指向的存储空间。被释放的空间通常被送入可用存储区池,以后可在调用malloc、realloc以及realloc函数来再分配。

       事实上, malloc()申请的时候实际上占用的内存要比申请的大。因为超出的空间是用来记录对这块内存的管理信息。

《UNIX环境高级编程》中第七章的一段话:

     大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等。这就意味着如果写过一个已分配区的尾端,则会改写后一块的管理信息。这种类型的错误是灾难性的,但是因为这种错误不会很快就暴露出来,所以也就很难发现。将指向分配块的指针向后移动也可能会改写本块的管理信息。

    从上面这段话可以看出,malloc()申请的空间分了两个不同性质的空间。一个就是用来记录管理信息的空间,另外一个就是可用空间了。用来记录管理信息的实际上是一个结构体。下面看看这个结构体的原型:

程序代码:
   struct mem_control_block {
    int is_available;  

     int size;   };


  下面是free()的源代码:

   // code...

       void free(void *ptr) 
    {
            struct mem_control_block *free;
            free = ptr - sizeof(struct mem_control_block);
            free->is_available = 1;
            return;
    }

   函数第二句将指向可用空间的指针倒回去,让它指向管理信息的那块空间,因为这里是在值上减去了一个结构体的大小!后面那一句free->is_available= 1实质是告诉操作系统那块内存可以去释放。


0 0