《高质量程序设计指南》第16章内存管理
其中,后四小节:16.12 用对象模拟指针,16.13 泛型指针auto_ptr,16.14 带有引用计数的智能指针,16.15 智能指针作为容器元素个人感觉比较难,有待以后深入学习。
前面几小节说的内容很重要,需要时常温故。
下面是一点笔记内容:
一内存的三种分配方式
1 从静态存储区域分配。特点:在程序编译的时候就已经分配好了,这些内存在程序的整个运行期间都存在,如全局变量、static变量等。
2 在堆栈上分配。特点:在函数执行期间,函数内局部变量(包括形参)的存储单元都创建在堆栈上,函数结束时,这些存储单元自动释放(堆栈清退)。注意:分配的内存容量有限,可能出现堆栈溢出。
3 从堆(heap)或自由存储空间上分配,也称动态内存分配。特点:在程序运行期间,调用malloc或new申请任意数量的内存,程序员自己掌握释放内存的恰当时机(使用free或delete)。
一般原则:如果使用堆栈存储和静态存储就能满足应用要求,就不要使用动态存储。
原因:在堆上动态分配内存需要很可观的额外开销。P287详述原因。
二常见的内存错误及其对策
1 内存分配未成功,却使用了它。
常用的解决办法是:在使用内存之前检查指针是否为NULL。如果指针P时函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查以避免输入非法参数。如果用malloc()或new来申请内存,应该用if(p==NULL)、if(p!=NULL)或者捕获异常来进行错误处理。
2 内存分配虽然成功,但是尚未初始化就使用它。
犯这种错误有两种原因:1 没有初始化的意识;2 误以为分配好的内存会自动初始化为全0,导致引用初值错误。
解决办法:我们应该在潜意识里认为全局对象或静态对象及其数组没有初值,无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。
3 内存分配成功且已经初始化,但操作越过了内存的边界。
例如:在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易高潮,导致数组元素访问越界。
4 忘记了释放内存或者只释放了部分内存,因此造成内存泄露
5 释放了内存却还在继续使用它。
见p288详述
规则1 用malloc或new申请内存之后,应立即检查指针值是否为NULL或者进行异常处理,以防止使用值为NULL的指针。
规则2 不要忘记初始化指针、数组和动态内存,防止将未初始化的内存作为右值使用。
规则3 避免数组或指针的下标越界,特别当心“多1”或者“少1”的操作。
规则4 动态内存的申请和释放必须配对,防止内存泄露。
规则5 用free或delete释放了内存之后,立即将指针设置为NULL,防止“野指针”。
三 free和delete把指针怎么啦!!!
free和delete只是把指针所指向的内存给释放掉,并没有把指针本身删除掉。如果不把这个指针设置为NULL,这个指针就变成了“野指针”。所以再次强调一定不要忘记初始化指针变量为NULL或者有效地址。
四、杜绝“野指针”
“野指针”不是NULL指针,而是指向“非法”内存的指针。程序员一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它可能不起作用。
“野指针”的成因主要有以下3种:
1 没有初始化指针变量。任何指针变量刚被创建时不会自动成为NULL指针,它的默认值是随机的,它会乱指一气。所以,指针变量在创建的同时应当初始化,要么将指针设置为NULL,要么让它指向有效的内存。
2 指针P被free()或delete之后,没有置位NULL,让人误以为p仍然是一个有效的指针。
3 指针操作超越了变量的作用范围,这种情况让人防不胜防。
五 malloc / free 和new / delete的不同
1 malloc()与free()是C/C++语言的标准库函数,new/delete是C++的运算符,它们都可用于申请和释放动态内存。
2 由于malloc()与free()是库函数而不是运算符,不在编译器控制权限之内,不能把调用构造函数和析构函数的任务强加给它们。
3 不要企图用malloc()与free()来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造和析构过程,对他们而言new/delete和malloc()/free()是等价的。
六 malloc / free的使用要点
函数malloc()的原型如下:
void *malloc(size_t size);
用malloc()申请一块长度为length的整型数组的内存,程序如下:
int *p = (int *)malloc(sizeof(int) * length);
我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。
1 malloc()函数返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void *转换成所需的指针类型;
2 malloc()函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。在malloc()函数的“()”中使用sizeof运算符是良好的风格。