注:想多了解malloc、calloc和realloc,请看博客:内存分配函数 http://blog.csdn.net/m0_38121874/article/details/75453598#
1、内存分配方式
(1)从静态存储区分配,如:全局变量、static变量等。
(2)在堆栈上分配,向下增长,如:局部变量、形式参数和返回值等。
(3)从堆或自由存储空间上分配,亦称为动态内存分配,如malloc函数和new运算符申请的内存(使用free和delete释放动态内存)。
2、常见的内存错误及其对策
(1)错误:内存分配未成功,却使用了它。
对策:在使用内存前检查是否有效。如果指针p是函数的参数,那么在函数的入口处用assert(p != NULL)进行检查以免输入非法参数。如果用malloc() 或new来申请内存,应该用if(p == NULL)、if(p != NULL)捕获异常来进行处理。
(2)错误:内存分配虽然成功,但是尚未初始化就使用它。
对策:不要忘记初始化指针、数组和动态内存,防止将未初始化的内存作为右值使用。
(3)错误:内存分配成功并初始化了,但是越界使用。
对策:避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
(4)错误:没有释放动态内存或者只释放了部分内存,造成了内存泄露。这种情况在使用动态分配的对象数组时极容易发生,比如:你无意中修改了指向动态数组的指针,或者使用错误的动态数组释放方法等。
对策:动态内存申请和释放必须配对,程序中malloc() 和free() 使用次数必须相同,防止内存泄露(new和delete也一样)。
(5)错误:释放了内存却还在继续使用它。有以下几种情况:
1)函数中return语句返回了栈内存的指针或者引用,因为该内存在函数结束时被自动释放。
2)释放了动态内存后,没有将指针置空,产生了野指针。
【例】
void Test(void)
{
char *str = (char *)malloc(100);
strcpy(str, "hello");
free(str);//str所指向的内存被释放,但是str本身仍然不变
//应加上str = NULL;这条语句
if (str != NULL)//没有起到防错作用
{
strcpy(str, "world");//运行时出现错误
printf(str);
}
}
3)多次释放同一块内存。
4)附加:指针操作超越了变量的作用范围。
【例】
class A
{
void Func(void)
{
cout << "A::func()" << endl;
}
};
void Test(void)
{ //外层程序块
A *p = NULL;
{ //内层程序块
A a;
p = &a; //a的生命周期在程序块内
}
p->Func(); //p是野指针
}
对策:用free函数和new运算符释放内存后立即将指针置空。
3、有了malloc/free为什么还要new/delete?
(1)malloc和free是C++/C语言的标准库函数,new/delete是C++的运算符,它们都可以用于申请内存/释放内存。
(2)C++语言需要一个能够完成动态内存分配和初始化工作的运算符new,以及一个能够完成清理与释放内存工作的运算符delete。并且,使用new/delete更加安全。
(3)malloc/free需要手动计算类型大小且返回值为void*,new/delete可以自己计算类型的大小,返回对应类型的指针。
4、既然new/delete的功能完全覆盖了malloc() 和free(),为什么C++还用malloc()和free()?
(1)C++程序会经常调用C程序,而C程序只能用malloc()/free()来管理动态内存。
(2)可以为自定义类重载new/delete,而malloc()和free()不能被任何类重载。
(3)在某些情况,malloc/free比new/delete效率高。
5、C++通过new和delete管理动态内存。
new/delete管理动态内存和new[]/delete[]管理动态内存。
【例】
void Test2()
{
int *p4 = new int;//动态分配4个字节(1个int)的空间单个数据
int *p5 = new int(3);//动态分配4个字节(1个int)的空间单个数据,并初始化为3
int *p6 = new int[3];//动态分配12个字节(3个int)的空间
int *p7 = (int*)malloc(sizeof(int));
delete p4;
delete p5;
delete []p6;
free(p7);
}
它们的操作过程如下图1:
图1 new/delete&new[]/delete[]操作过程图
6、new的三种使用方式
(1)plain new/delete
其原型为:
void *operator new(std::size_t)throw(std::bad_alloc);
void operator delete(void *)throw();
plain new在失败后抛出标准异常std::bad_alloc而不是返回NULL。
【例】
char *Getmemory(unsigned long size)
{
char *p = new char[size];
return p;
}
void main()
{
try
{
char *p = Getmemory(1000000); //可能抛出std::bad_alloc异常
//...
delete[]p;
}
catch (const std::bad_alloc&ex)//??????
{
cout << ex.what() << endl;
}
}
(2)nothrow new/delete
其原型为:
void *operator new(std::size_t, const std::nothrow_t&)throw();
void operator delete(void *)throw();
Nothrow new就是不抛出异常的运算符new的形式,nothrow new在失败时返回NULL。Nothrow是C++语言实现定义的一个nothrow_t 的全局const对象,作为new的标志性哑元而已。
【例】
void func(unsigned long length)
{
unsigned char *p = new(nothrow)unsigned char[length];
if (p == NULL)
{
cout << "allocte failed!" << endl;
}
//...
delete[]p;
}
(3)placement new/delete
Placement意为“放置”,这种new形式允许在一块已经分配成功的内存上重新构造对象或者对象数组,所以它不用担心内存分配失败。
注:
1)placement new的主要用途就是:反复使用一块较大的动态分配成功的内存来构造不同类型的对象或者它们的数组。
【例】先申请一个足够大的字符数组内存,然后当需要时在它上面构造不同类型的对象或数组。
#include<iostream>
#include<new>
using namespace std;
void main()
{
char *p = new(nothrow)char[100];
if (p == NULL)
{
cout << "allocte failed!" << endl;
exit(-1);
}
//...
long *q1 = new(p) long(100);//placement new不担心失败
//...
int *q2 = new(p)int[100 / sizeof(int)];//构造不同的数组
//...
ADT *q3 = new(p)ADT[100 / sizeof(ADT)];
//...
delete[]p;//释放内存
}
2)placement new构造起来的对象或者其数组的大小并不一定等于原来分配的内存大小,因此使用delete会造成内存泄露,或者在之后释放内存时会出现运行错误,应该显示地调用它们的析构函数来销毁(析构函数并不释放对象的内存)。
【例】
#include<iostream>
#include<new>
using namespace std;
void main()
{
char *p = new(nothrow)char[sizeof(ADT) + 2];
if (p == NULL)
{
cout << "allocte failed!" << endl;
exit(-1);
}
ADT *q = new(p)ADT;
//...
//delete q; //错误!不能再此处释放内存
q->ADT::~ADT(); //显示调用析构函数
delete[]p; //再释放内存
}
7、内存耗尽怎么办?
如果在申请动态内存时找不到足够大的连续字节内存块,通常有如下几种方式处理“内存耗尽”的问题。
(1)判断指针是否为NULL,如果是则立刻用return语句终止本函数。
【例】
void Func(void)
{
A *a = new(nothrow)A;
if (a == NULL)
{
return;//终止本函数
}
...
}
(2)判断指针是否为NULL,如果是立刻用exit(1)终止整个程序的运行。
【例】
void Func(void)
{
A *a = new(nothrow)A;
if (a == NULL)
{
exit(1);//终止整个程序的运行
}
...
}
(3)为new和malloc()预设异常处理函数。
(4)捕获new抛出的异常,并尝试修复。
注意:
1)内存耗尽对应用系统来说已经无药可救了,如果不用exit(1)终止程序,可能会对操作系统有害。
2)对于32位系统以上的应用系统而言,一般情况下使用malloc()和new几乎不可能导致“内存耗尽”,因为32位操作系统支持“虚存”,内存用完了,自动用硬件空间顶替。
3)不将错误处理将影响程序质量。
8、扩展学习
古代政权体系里面百官上朝,官员上折奏报的情景。如下图2:
图2 百官上朝图
而计算机的存储硬件体系设计跟皇权体系中的信息传递有异曲同工之妙,如下图3所示:
图3 硬件体系简易比拟设计图
计算机的硬件存储体系层次结构图,如下图4所示:
图4 存储器层次结构图