1.内存分配方式
(1)从静态存储区域分配。例如全局变量,static 变量。
(2)在栈上创建。例如局部变量。
(3)从堆上分配,亦称动态内存分配。例如malloc、free以及new、delete。
2.常见的内存错误及其对策
(1)内存分配未成功,却使用了它。
常用解决办法是,在使用内存之前检查指针是否为NULL。可以用判断空或者断言检查。
(2)内存分配虽然成功,但是尚未初始化就引用它。
不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
(3)内存分配成功并且已经初始化,但操作越过了内存的边界。
(4)忘记了释放内存,造成内存泄露。
(5)释放了内存却继续使用它。
函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁;使用free 或delete释放了内存后,没有将指针设置NULL。
3.指针数组的区别
(1)分配内存的存储位置不一样。指针在堆区,数组在栈或者静态存储区。
char *p = “world”; // 注意p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行会出错。该语句企图修改常量字符串的内容
(2)用运算符sizeof 可以计算出数组的容量(字节数)。但是sizeof(p)的值却是4。
注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
4.如果函数的参数是一个指针,不要指望用该指针去申请动态内存。因为该参数是局部变量,会随着函数调用的完成而释放掉。
可以通过下面的两种方法实现:
(1)char *GetMemory3(int num)
{
char *p = (char *)malloc(sizeof(char) * num);
return p;
}
这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡。如下例:
char p[] = "hello world";
return p; // 编译器将提出警告
然而(1)例的p是指向malloc分配的内存。
(2)void GetMemory2(char **p, int num)
{
*p = (char *)malloc(sizeof(char) * num);
}
5.free和delete只是把p指向的存储区域释放掉,而p本身并没有释放掉。因此在释放掉p所指向的存储区域之后,一定记得将p赋空,防止对未定义区域的操作。
6. 如下代码
void Test(void)
{
A *p;
{
A a;
p = &a; // 注意 a 的生命期
}
p->Func(); // p 是“野指针”
}
a的生命期仅限于内层花括号内。
7. 对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free 淘汰出局呢?这是因为C++程序经常要调用C 函数,而C程序只能用malloc/free 管理动态内存。
8.内存耗尽怎么办?
如果在申请动态内存时找不到足够大的内存块,malloc 和new 将返回NULL指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。
(1)判断指针是否为NULL,如果是则马上用return 语句终止本函数。
(2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。
(3)为new 和malloc 设置异常处理函数。例如VisualC++可以用_set_new_hander 函数为new 设置用户自己定义的异常处理函数,也可以让malloc 享用与new相同的异常处理函数。
注意:上述(1)(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果不用exit(1)把坏程序杀死,它可能会害死操作系统。
9. malloc/free 的使用要点
(1)malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。
(2)malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。在malloc的“()”中使用sizeof 运算符是良好的风格。
(3)为什么free 函数不象malloc 函数那样复杂呢?这是因为指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p是NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。
10. new/delete 的使用要点
运算符new 使用起来要比函数malloc 简单得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];
这是因为new 内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new 的语句也可以有多种形式。例如:
Obj *a = new Obj;
Obj *b = new Obj(1); // 初值为1
如果用new 创建对象数组,那么只能使用对象的无参数构造函数。
在用delete 释放对象数组时,留意不要丢了符号‘[]’。例如
delete []objects; // 正确的用法
delete objects; // 错误的用法
后者相当于delete objects[0],漏掉了另外99 个对象。