1
引言
在程序执行时,访问内存是必需的,总的来说在
C ++
语言中,内存分配有下述二种力一式:
在静态存储区中分配,也称静态内存分配在这种分配方式中,内存在程序编译时就己经分配好,这块内存在程序的整个运行期问都存在。全局变量、
static
变量占用的内存在静态存储区中,这此变量我们称之为静态对象。
在栈中分配,也称局部内存分配。函数内的局部变量、函数的形式参数、函数执行结束后返回的地址等都是在栈中分配内存,函数执行结束,这此内存单元通过执行出栈指令释放。栈内存分配和释放指令内置于处理器的指令集中,效率很高。在栈中分配的对象,我们称之为局部对象。不过,局部对象的内存分配和回收不用程序管,在程序执行时由执行系统动态进行。
在堆中分配,也称动态内存分配程序在运行的时候用操作符
new
申请长度任意的内存,不再需要时用操作符
delete
释放内存。在堆中分配的对象,我们称为动态对象。
在栈上创建对象既方便又理想,但更一般的情况是,在程序执行的任何时候,需要根据来自程序外部的信息创建对象和销毁对象,这就需要对内存进行动态分配和回收,而且这部分工作必须由程序来管,所以也是
C++
内存分配和回收的重点和难点。下面讨论在
C ++
程序设计中进行内存动态分配和回收的方法及存在的问题和解决方法。
2
数组对象的动态分配和回收
在
C ++
程序中,数组的内存分配有两种,静态分配和动态分配。在编译时进行的分配我们称为静态分配,在执行时进行的分配我们称为动态分配。对于一般的程序设计者,习惯更多地使用静态数组。然血静态数组的长度在编译时就确定了,有时不能满足应用的需要,更一般的情况是,数组的长度在程序执行时根据输入的数据才能确定,如求
n
个整数的最大数,
n
的值从键盘接收,此时,存放
n
个整数的数组必须在执行时用
new
操作符动态创建。完整的例程如下:
#include <iostream.h>
voidmain()
{cout<<"Please input a integer to n:";
cin>>n;
int *p=new int[n];
//
为长度为
n
的值确定的动态一维数组分配内存
max=a[0];
for (int i=1;i<n;i++)
if (a[i]>a[0])a[0]=a[i];
cout<<"The max value is:"+a[0];
delete[] p;//
动态删除数组
i.
用的主存空间
}
如果我们使用静态数组来存放
n
个数据,假定在源程序中符号常量
n
初始化为
100
,那么程序只能处理
100
个数据以内的整数,可见程序的使用范围小,也可以说程序执行时的灵活性小。从上述例子中我们可以看出使用动态数组的好处,即程序能处理任意个同类型的数据,只要主存空间不受限制。
在使用动态数组时必须特别注意,当数组不再使用,应该用
delete[]
操作符及时删除,使得其占用过的内存能再次利用,否则,随着系统不断动态分配内存,可用的主存空间会越来越小,甚至会耗尽。
相对一维数组的动态分配形式,多维数组的动态分配形式相对复杂,且初学者使用时非常容易出错。下面描述了二个多维数组的动态分配形式。
char **p1=new char[3][n];
double ***p2=new double[4][m][n];
//
动态创建
2
维、
3
维数组
,m,n
可以是常量或变量
在创建多维数组时,必须十分注意指针
p1
和
p2
的类型,稍不注意,会将
p1
和
p2
声明为
char *pl
和
double *p2
,导致编译时产生和类型有关的错误。另外,创建多维数组的第一维的大小必须由常量指定。
关于多维数组的回收和一维数组形式一致,下面的两种形式表示删除上述创建的二维和二维数组。
(1 ) delete p1 [];
(2) delete p2[];
3
一般对象的动态分配和回收
非数组对象
(
一般对象
)
的动态分配和回收相对于数组对象的创建和回收,要简单此。
3.1
一般对象的动态分配和回收
C ++
语言中用
new
操作符为一般对象动态分配内存。为能够在程序中存取
new
所创建的对象,必须使一个指针指向所创建的对象。例如,下述程序段创建一个初值为
(5,5)
的的屏幕上的点类
point
的对象,指向该对象的指针仇被赋给了指针对象
ptr
:
class point//
屏一
SIT
上的点类
{
x,y;
public:
point(int x1=0, int y1=0){x=x1 ;y=y1;}
int getx(){returnx;}
int gety(){returny;}
};
voidmain()
{point *ptr=newpoint(5,5);
//
创建
point
类型的对象,初始值为
(5,5)
Cout<<"x="<<pt->getx()+'\t'<<'y='<<pt->gety()+endl;
……
delete p;
常量对象的生存期也用
delete
来结束,如上面的一行语句。与创建的非常量对象不完全相同,创建的
const
对象有一些特殊的属性。首先,
const
对象必须被初始化,如果省略了括号中的初始值,就会产生编译错误。第二,用
new
表达式返回的值作为初始值的指针必须是一个指向
const
类型的指针。此外,我们不能创建元素类型为基木数据类型的
const
数组,因为
const
数组不能被初始化,如象下而那样声明
const
数组会导致编译错误:
const int *pt=new const int[10];
3.3
定位
new
表达式
new
表达式还允许将对象创建在己被分配好的内存中。这种形式的
new
表达式被称为定位
new
表达式,其一般形式为:
new (place_ address) type
其中
place_ address
是个指针表达式,
I(IJ type
是个类型表达式。必须注意,为了使用这种形式的二
w
表达式,我们必须包含头文件
<new>
。这个功能允许程序员预分配大量的内存供以后通过这种形式的
new
表达创建对象。请看下面的例子:
#include <iostream>
#include <new>
using namespace std;
const int num = 10;
class block
{
int val;
public:
int block(int a=0){val=a;}
int getval(){ return val;}
};
char 'buf=new char[sizeof(block)'num];
//
预分配内存,但没有
block
对象
voidmain()
{block 'p=new (buf) block;
if (p->getval() == 0)//
检查一个
block
对象是否被放在
buf
中
cout<<"new expression worked!"+endl;
delete[] buf;//
删除
buf
指
I}}J
的字符缓冲,之后不能再访
不存在与定位
new
表达式相匹配的
delete
表达式。其实我们并不需要这样的
delete
表达式,因为定位
new
表达式并不分配内存。在上面的例了中,我们删除的不是
p
指向的内存,而是
buf
指向的内存。当程序不再需要字符缓冲时,
buf
指向的内存被删除,此时字符缓冲中的任何对象的生命期都结束了,在上面的例了中,
p
就不再指向一个有效的
block
类的对象了。
4
使用动态内存常见的问题与对策
和
C
语言提供的动态内存分配和回收方法相比,
new
和
delete
比
C
语言的
malloc()
和
free()
函数使用起来更加方便和安全,但同时也增加了各种错误发生的机会,我们总结了如下几个方面的错误。
4.l
未使用相同形式的
new
和
delete
首先我们来看下面的语句序列
:
int *pt=new int[30];
……
delete pt;
这两条语句看似没有问题,而且编译器也不会给出错误或警告信息,其实己经发生了内存泄漏。因为
30
个整型对象有
29
个都未被释放内存空问,上述代码表示释放了数组的第一个元素对象。为什么会这样呢,因为在使用
delete
表达式时,编译器不知道即将被删除的指针指向的是单一对象或数组对象,它只会根据
delete
的使用形式来判断,带了下标符“
[]
”的则认为是删除数组对象,否则就认为是删除单一对象。因此,解决该问题的方法很简单,当使用
new
时带了下标符“
[]
”,使用
delete
时也应带
[]
;当你使用
new
时未带“
[]
”,使用
delete
时也不应带“
[]
”。
4.2
内存不足的问题
如果
new
操作找不到足够大的内存块,则引发
bad_alloc
标准类型的异常,为了提高程序的可靠性,程序中必须能捕获这种内存分配失败引发的异常,这是一般程序员容易忽视的问题。不过因为异常处理执行效率不高,所以可用下述方法使
new
操作返回一个
NULL
而不抛出一个异常。例如程序段:
B*p=new (nothrow) B;/*
对象
nothrow
是在
new
中定义的常量,
指出如果
new
操作失败则返回
NULL
而不产生异常
*/
if (!p){cout<<"allocation failure"<<endl;return;}
4.3
内存被释放后使用指针的问题
指向并不存在的内存的指针,我们称之为“野指针”。例如程序段
:
char *p=new char[6];
strcpy(p,"hello");
delete[] p;//
此时
p
成了野指针
……
if (p) strcpy(p,"world");//
执行时出错
……
为了能有效解决上述问题,一个简单的办法就是在内存释放后,将指针赋值为
VULI
。如
:
delete[] p;
p=N ULL;
4.4
没有和构造函数配对的析构函数
如果在一个类对象在创建时要动态分配内存,就会在构造
函数中用
new
分配。例如
:
class array
{int *p;
public:
array(int i){p=new int[i];}...
};
很明显,在构造函数中为
array
类对象分配的内存一直不能被释放,随着对象的多次产生,就会产生严重的内存泄漏问题。解决办法是为这种类增加这样一个析构函数
:array ::array{delete [] p;}
。
5
结束语
在
C ++
程序设计中,会经常进行内存的动态分配和回收。木文对
C++
的动态内存技术进行了讨论,并对常见问题提出了可行的解决方法。其实,用好
new
和
delete
是一门技术,它能减少程序中与使用指针有关的运行错误,从而减轻调试程序的难度,另外它还能提高程序的可靠性(健壮性)。