C++ ---释放内存(new和delete)

                                                                                        C++ ---释放内存(new和delete)

C++动态分配和释放内存 @c.biancheng.net/view/206.html

-------------------------------------------------------------------------------------------------

C语言中,动态分配内存用 malloc() 函数,释放内存用 free() 函数。如下所示:

  1. int *p = (int*) malloc( sizeof(int) * 10 ); //分配10个int型的内存空间
  2. free(p); //释放内存

C++中,这两个函数仍然可以使用,但是C++又新增了两个关键字,new 和 delete:new 用来动态分配内存,delete 用来释放内存。

用 new 和 delete 分配内存更加简单:

  1. int *p = new int; //分配1个int型的内存空间
  2. delete p; //释放内存

new 操作符会根据后面的数据类型来推断所需空间的大小。

如果希望分配一组连续的数据,可以使用 new[]:

  1. int *p = new int[10]; //分配10个int型的内存空间
  2. delete[] p;

用 new[] 分配的内存需要用 delete[] 释放,它们是一一对应的。

和 malloc() 一样,new 也是在堆区分配内存,必须手动释放,否则只能等到程序运行结束由操作系统回收。为了避免内存泄露,通常 new 和 delete、new[] 和 delete[] 操作符应该成对出现,并且不要和C语言中 malloc()、free() 一起混用。

在C++中,建议使用 new 和 delete 来管理内存,它们可以使用C++的一些新特性,最明显的是可以自动调用构造函数和析构函数,后续我们将会讲解。

--------------------------------------------------------------------------------------------------------------

数组的长度是预先定义好的,在整个程序中固定不变。C++ 不允许定义元素个数不确定的数组。例如:

  1. int n;
  2. int a[n]; //这种定义是不允许的

但是在实际的编程中,往往会出现所需的内存空间大小取决于实际要处理的数据多少,而实际要处理的数据数量在编程时无法确定的情况。如果总是定义一个尽可能大的数组,又会造成空间浪费。何况,这个“尽可能大”到底应该多大才够呢?

为了解决上述问题,C++ 提供了一种“动态内存分配”机制,使得程序可以在运行期间,根据实际需要,要求操作系统临时分配一片内存空间用于存放数据。此种内存分配是在程序运行中进行的,而不是在编译时就确定的,因此称为“动态内存分配”。

在 C++ 中,通过 new 运算符来实现动态内存分配。new 运算符的第一种用法如下:

T *p = new T;

其中,T 是任意类型名,p 是类型为 T* 的指针

这样的语句会动态分配出一片大小为 sizeof(T) 字节的内存空间,并且将该内存空间的起始地址赋值给 p。例如:

  1. int* p;
  2. p = new int;
  3. *p = 5;

第二行动态分配了一片 4 个字节大小的内存空间,而 p 指向这片空间。通过 p 可以读写该内存空间。

new 运算符还有第二种用法,用来动态分配一个任意大小的数组:

T *p =new T[N];

其中,T 是任意类型名,p 是类型为 T* 的指针,N 代表“元素个数”,可以是任何值为正整数的表达式,表达式中可以包含变量、函数调用等。这样的语句动态分配出 N × sizeof(T) 个字节的内存空间,这片空间的起始地址被赋值给 p。例如:

  1. int* pn;
  2. int i = 5 ;
  3. pn = new int[i*20];
  4. pn[0] = 20 ;
  5. pn[100] = 30;

最后一行编译时没有问题,但运行时会导致数组越界。因为上面动态分配的数组只有 100 个元素,pn[100] 已经不在动态分配的这片内存区域之内了。

如果要求分配的空间太大,操作系统找不到足够的内存来满足,那么动态内存分配就会失败,此时程序会拋出异常。关于这一点,将在后续章节中介绍。

程序从操作系统动态分配所得的内存空间在使用完后应该释放,交还操作系统,以便操作系统将这片内存空间分配给其他程序使用。C++ 提供 delete 运算符,用以释放动态分配的内存空间。delete 运算符的基本用法如下:

delete p;

p 是指向动态分配的内存的指针。p 必须指向动态分配的内存空间,否则运行时很可能会出错。例如:

  1. int* p = new int;
  2. *p = 5;
  3. delete p;
  4. delete p; //本句会导致程序出错

上面的第一条 delete 语句正确地释放了动态分配的 4 个字节内存空间。第二条 delete 语句会导致程序出错,因为 p 所指向的空间已经释放,p 不再是指向动态分配的内存空间的指针了。

如果是用 new 的第二种用法分配的内存空间,即动态分配了一个数组,那么释放该数组时,应以如下形式使用 delete 运算符:

delete[] p;

p 依然是指向动态分配的内存的指针。例如:

  1. int* p = new int[20];
  2. p[0] = 1;
  3. delete[] p;

同样地,要求被释放的指针 p 必须是指向动态分配的内存空间的指针,否则会出错。

如果动态分配了一个数组,但是却用delete p的方式释放,没有用[],则编译时没有问题,运行时也一般不会发生错误,但实际上会导致动态分配的数组没有被完全释放。

牢记,用 new 运算符动态分配的内存空间,一定要用 delete 运算符释放。否则,即便程序运行结束,这部分内存空间仍然不会被操作系统收回,从而成为被白白浪费掉的内存垃圾。这种现象也称为“内存泄露”。

如果一个程序不停地进行动态内存分配而总是没有释放,那么可用内存就会被该程序大量消耗,即便该程序结束也不能恢复。这就会导致操作系统运行速度变慢,甚至无法再启动新的程序。但是,只要重新启动计算机,这种情况就会消失。

编程时如果进行了动态内存分配,那么一定要确保其后的每一条执行路径都能释放它。

另外还要注意,释放一个指针,并不会使该指针的值变为 NULL。

---------------------------------------------------------------------------------------

通常定义变量或者对象,编译器在编译时都可以根据该变量或对象的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间,这种内存分配被称为静态存储分配。

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

  所有动态存储分配都在堆区中进行。

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

  在C++中,申请和释放堆中分配的存储空间,分别使用new和delete的两个运算符来完成,使用格式如下:
  指针变量名 = new 类型名(初始化式)
    delete指针名 
  new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而动态创建的对象本身没有名字。

复制代码
#include<iostream>
using namespace std;
class ST
{
private:
    int a;
public:
    ST(int _a=0):a(_a)
    {
        this->a = _a;
        cout<<"Object was built. "<<endl;
    }
    ~ST()
    {
        cout<<"Object was free. "<<endl;
    }
};
void malloc_free()
{
    ST *tmp = (ST*)malloc(sizeof(ST));
    free(tmp);
}
void new_delete()
{
    ST *tmp = new ST[1];//但是new为对象数组分配空间不可初始化
    delete []tmp;
}
void main()
{
    ST *pt = new ST(1);//new为对象分配空间就可初始化
    delete [] pt;//delete p 是删除pt所指空间,而并非删除pt,为了预防野指针可将pt=NUll
    malloc_free();
    new_delete();
}
复制代码

 运行结果

malloc&free,new&delete都是申请释放空间,但是,有如下几点不同

1.new申请时不需要强制转换类型,也不需要申请结束后判断是否申请到(因为其内部含有未申请到异常退出)

2.new在为某个对象申请空间时,会调用构造函数,因此可在申请时初始化(对象的构造函数要支持),delete也会在释放空间时会先调用析构函数

3.由堆区创建对象数组(例如ST*pt = new ST[10]),只能调用缺省的构造函数(缺省构造函数:不含参数的或每个参数都有默认值的构造函数),不能调用其他任何构     造函数。若没有缺省的构造函数则不能创建对象数组。还有,创建对象数组时不可以初始化。

4.对数组进行动态分配格式如下:
 指针变量名 = new 类型名[下标表达式]

 (下标表达式不是常量表达式,可不必再编译时确定)
 delete []指向该数组的指针变量名
 这两者必须搭配使用,若delete未加[],编译器会认为该指针是指向数组的第一个元素的指针,仅仅回收第一个元素所占空间。加上[]则会回收整个数组。

@https://www.cnblogs.com/area-h-p/p/10339599.html

-------------------------------------------------------

new 和 delete 是 C++ 用于管理堆内存的两个运算符,对应于C语言中的 malloc 和 free,但是 malloc 和 free 是函数,而new 和 delete 是运算符。除此之外,new 在申请内存的同时,还会调用对象的构造函数,而 malloc 只会申请内存;同样,delete 在释放内存之前,会调用对象的析构函数,而 free 只会释放内存。

C++
new运算符申请内存:
将调用相应的 operator new(size_t) 函数动态分配内存,在分配到的动态内存块上 初始化 相应类型的对象(构造函数)并返回其首地址。如果调用构造函数初始化对象时抛出异常,则自动调用 operator delete(void*, void*) 函数释放已经分配到的内存。

delete运算符释放内存:
调用相应类型的析构函数,处理类内部可能涉及的资源释放,调用相应的 operator delete(void *) 函数。

int main()
{
    T * t = new T(); // 先内存分配,再构造函数
    delete t; // 先析构函数,再内存释放
    return 0;
}

new表达式

type  * p_var = new type;   
//分配内存,但未初始化
int * a = new int;

type  * p_var = new type(init);
//分配内存时,将 *a 初始化为 8
int * a = new int(8);

type *p_var = new type [size];
//分配了3个int大小的连续内存块,但未初始化
int * a = new int[3] ;

delete表达式

删除单变量地址空间
int *a = new int;
delete a;//释放单个int的空间

删除数组空间
int *a = new int[5];
delete []a;//释放int数组空间


C
内存区域可以分为栈,堆,静态存储区和常量存储区。局部变量,函数形参,临时变量都是在栈上获得内存的,它们获取的方式都是由编译器自动执行的。
而C标准函数库提供了许多函数来实现对堆上内存管理,其中包括:malloc函数,free函数,calloc函数和realloc函数。使用这些函数需要包含头文件stdlib.h。

(1)malloc函数
malloc函数可以从堆上获得指定字节的内存空间,其函数声明如下:
void * malloc(int n);


其中,形参n为要求分配的字节数。如果函数执行成功,malloc返回获得内存空间的首地址;如果函数执行失败,那么返回值为NULL。由于malloc函数值的类型为void型指针,因此,可以将其值类型转换后赋给任意类型指针,这样就可以通过操作该类型指针来操作从堆上获得的内存空间。

需要注意的是,malloc函数分配得到的内存空间是未初始化的。因此,一般在使用该内存空间时,要调用另一个函数memset来将其初始化为全0。memset函数的声明如下:

void * memset (void * p,int c,int n) ;


该函数可以将指定的内存空间按字节单位置为指定的字符c。其中,p为要清零的内存空间的首地址,c为要设定的值,n为被操作的内存空间的字节长度。

int * p=NULL;
p=(int *)malloc(sizeof(int));
if(p==NULL){
    printf(“Can’t get memory!\n”);
}
memset(p,0,siezeof(int));

(2)free函数
从堆上获得的内存空间在程序结束以后,系统不会将其自动释放,需要程序员来自己管理。一个程序结束时,必须保证所有从堆上获得的内存空间已被安全释放,否则,会导致内存泄露。
void free (void * p);


由于形参为void指针,free函数可以接受任意类型的指针实参。
但是,free函数只是释放指针指向的内容,而该指针仍然指向原来指向的地方,此时,指针为野指针,如果此时操作该指针会导致不可预期的错误。安全做法是:在使用free函数释放指针指向的空间之后,将指针的值置为NULL。

free(p);
p=NULL;
//注:使用malloc函数分配的堆空间在程序结束之前必须释放

(3)calloc函数
calloc函数的功能与malloc函数的功能相似,都是从堆分配内存。其函数声明如下:
void *calloc(int n,int size);

函数返回值为void型指针。如果执行成功,函数从堆上获得size X n的字节空间,并返回该空间的首地址。如果执行失败,函数返回NULL。该函数与malloc函数的一个显著不同时是,**calloc函数得到的内存空间是经过初始化的,其内容全为0。**calloc函数适合为数组申请空间,可以将size设置为数组元素的空间长度,将n设置为数组的容量。

 int * p=NULL;
//为p从堆上分配SIZE个int型空间

p=(int *)calloc(SIZE,sizeof(int));

if(NULL==p){
    printf("Error in calloc.\n");
    return -1;
}

free(p);
p = NULL;

(4)realloc函数
realloc函数的功能比malloc函数和calloc函数的功能更为丰富,可以实现内存分配和内存释放的功能,其函数声明如下:
void * realloc(void * p,int n);

其中,指针p必须为指向堆内存空间的指针,即由malloc函数、calloc函数或realloc函数分配空间的指针。realloc函数将指针p指向的内存块的大小改变为n字节。如果n小于或等于p之前指向的空间大小,那么。保持原有状态不变。如果n大于原来p之前指向的空间大小,那么,系统将重新为p从堆上分配一块大小为n的内存空间,同时,将原来指向空间的内容依次复制到新的内存空间上,p之前指向的空间被释放。relloc函数分配的空间也是未初始化的。

 int * p=NULL;
p=(int *)malloc(sizeof(int));

p=(int *)realloc(p,3*sizeof(int));

//释放p指向的空间
realloc(p,0);
p=NULL;

注:使用malloc函数,calloc函数和realloc函数分配的内存空间都要使用free函数或指针参数为NULL的realloc函数来释放。
 
原文链接:https://blog.csdn.net/dongxianfei/article/details/79031943

posted on 2019-09-20 09:38  WP的烂笔头  阅读(46055)  评论(0编辑  收藏  举报