C++动态内存的申请和释放


前言

最近在学C++,总结一下在堆区申请动态内存空间的使用和释放问题。并通过几个示例代码说明在释放内存时容易发生的错误。内容并不涉及深层次的东西。文中可能有不恰当或者错误的地方,欢迎评论指正!

对于新手来说,动态内存的操作真是令人头大。
因为在学习过程中代码量很少,使用的内存也很小,就算不释放内存空间也不会出现什么问题,既不会报错,也不会因为内存泄漏造成什么影响。
但在释放内存之后,很多时候编译可以通过,但是由于操作不当,程序运行时就会崩溃。又不会指出哪里的问题,导致排错的过程又很麻烦。就算找出了错误,又不明白为什么错了。
所以做了以下笔记,记录了正确的操作方法和容易犯的错误。

需要注意的是:
我们申请和释放的一直都是内存空间,而不是那个指针。指针的作用只是方便我们对内存进行操作。

一、内存申请与释放

1. 关键字 new

在C/C++中使用指针来保存一块内存的地址,如使用语句int * p;定义一个指针 p 来指向一个 int 类型的地址,也可以说变量 p 用来保存一个整形数据的地址。

在C++中,关键字 new 会在堆区申请一块动态内存,并返回动态分配内存块的首字节地址,如语句new char;会申请一块 char 类型的内存,new int;会申请一块 int 类型的内存。但我们并不知道这块内存在哪,因为动态内存没有名字,所以我们可以定义一个指针来接收这个地址:int * p = new int;,还可以用语句int * p = new int(10);来初始化这块内存的值为10,或者用语句int * p = new int[5];申请一个数组,这时 p 保存数组的首地址。

下图是内存分配的示意图:
内存申请
第一条语句申请了一个 int 类型的存储空间,这相当于告诉系统:“ 0x5CFC ~ 0x5CFF 这块空间现在归我了,你就别管了!( ^ _ ^ ) ”
第二条语句申请了一个有5个元素的数组,相当于告诉系统:“ 0x5D04 ~ 0x5D17 这一大块空间现在都归我管了。(* ^ o ^) ”(int 类型一般占4字节)

如果申请成功的话,你就得到一块属于自己的空间了。在之前的编译器中,申请失败会返回 NULL,但现在大多数编译器在申请失败时会抛出异常。
这时我们可以使用 *p1 来获取数值10,还可以像指针和数组那样操作 p2 ,如右下角方框中的两条语句。

2. 关键字 delete

上面申请的内存空间如果没有释放的话,会在程序结束后由系统回收。这在申请少量内存时,程序运行并不会出现什么影响,只是浪费系统内存。但在大型项目中,如果不及时释放掉不用的内存,就会导致程序运行速度减慢甚至系统崩溃等严重后果。

在C++中,使用关键字 delete 来释放通过 new 申请到的动态内存,如下面两条代码:

delete p1;	//释放p1指向的内存
delete[] p2;//释放p2指向的数组内存

释放内存就相当于告诉系统:“这块内存空间我用完了,你也可以使用了。( * v * ) ”

注意:
1、这里 delete 是释放了指针所指向的内存空间,并不是删除了指针
2、释放数组内存时不加“ [ ] ”只会释放第一个元素所占的空间

当语句delete p1;执行完之后,有的编译器 p1 依然指向那块地址,有的编译器会指向其他地方,这样的指针被叫做野指针,当你不小心又用了这个指针后就会出现乱七八糟的东西。所以为了防止这样的事情发生,我们释放内存之后让指针指向 NULL,这样的指针被叫做空指针 。完整的代码如下:

int * p1 = new int(10);	//分配1个int型的内存空间
delete p1;				//释放p1指向的内存
p1 = NULL;				//使指针p1指向NULL

int * p2 = new int[5];	//分配5个int型的内存空间
delete[] p2;			///释放p2指向的数组内存
p2 = NULL;				//使指针p2指向NULL

二、释放内存后的崩溃瞬间

以下的代码都是错误示例,在编写程序时需要注意。

1. 重复释放内存

当你写了如下代码:

int * p1 = new int;
delete p1;
delete p1;	//释放野指针

编译是可以通过的,但是程序运行时就会崩溃。当然,我们编写代码时并不会连续写两条相同的 delete,但很多时候两条语句并不在一起,或者操作的并不是同一个指针,就像下面这样:

int * p1 = new int;
int * p2 = p1;
delete p1;
delete p2;	//重复释放内存

虽然不是同一个指针,但释放的是同一块内存空间,本质上和上面是一样的(也许编译器的处理方法不一样,这里不做讨论)。

C++ 类的拷贝构造函数会涉及到深拷贝和浅拷贝,浅拷贝有时也会出现重复释放内存的问题。

2. 释放非堆区内存

当你释放的内存不是申请的动态内存,也就是非堆区内存,也会造成程序崩溃:

int a;
int * p1 = new int;
p1 = &a;
delete p1;	//释放非堆区内存

我们学 C/C++ 的指针就是为了指来指去的,但是当指向动态内存的指针,在释放该内存之前就指向了其他地方,这样最后 delete 释放的并不是动态内存空间,而真正的需要释放的地址已经找不到了。所以在使用指向动态内存的指针时一定要小心,不然程序崩溃了,你也崩溃了。

我们可以使用 const 关键字来修饰指针,如int * const p1 = new int;,之后指针再指向其他地址就会报错。只是这样在后面释放内存之后,这个指针也不能再使用了。

三、使用释放后的内存(Dev-C++)

在Dev-C++中,执行delete p1;之后,p1仍指向原来的空间,只是内存中的值改变了,但仍然可以使用 *p1 来读写,只是这样是做是非常危险的,比如下面代码:

#include <iostream>
using namespace std;

int main(void)
{
	int * p1 = new int(10);
	cout << "1.*p1: " << *p1 << "\tp1: " << p1 << endl;
	delete p1;
	cout << "2.*p1: " << *p1 << "\tp1: " << p1 << endl;
	*p1 = 20;
	cout << "3.*p1: " << *p1 << "\tp1: " << p1 << endl;

	return 0;
}

运行结果如下:
运行结果
以上代码在 VS2017 中编译可以通过,但程序运行时会崩溃。暂时不知道原因,可能是编译器不一样(Dev-C++编译器是GCC,Visual Studio 的默认编译器是 MSVC),也可能是开发环境不一样。


结语

C/C++ 的指针给我们带来更多便利的同时,也赋予我们更多的责任。很多代码错误也许编译时并不会报错,但是会造成严重的后果。我们必须对自己申请的内存负责,对我们的代码负责。

注:文中的代码都是在 Windows 7 操作系统下的 Dev-C++ 和 vs2017 中进行测试的,不同的开发环境可能会产生不同的结果。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值