1. 内存的分配方式
内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的
整个运行期间都存在。例如全局变量,static 变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函
数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集
中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意
多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存
期由我们决定,使用非常灵活,但问题也最多。
2. 常见内存错误
(1) 内存分配未成功,却使用了它
解决办法:
在使用之前检查指针是否为NULL。如果指针p是函数的参数,那么可以在函数的入口处用assert(p!=NULL)
进行查错。如果是用malloc 或 new 来申请内存,应该用 if(p==NULL) 或 if(p!=NULL)进行防错处理。
(2) 内存分配虽然成功,但是尚未初始化就引用它。
解决办法:
分配内存之后及时初始化。
(3) 内存分配成功并且已经初始化,但操作越过了内存的边界。
(4) 忘记了释放内存,造成内存泄露。
解决办法:
动态内存的申请与释放必须配对,程序中malloc 与free 的使用次数一定要相同,否则肯定有错误(new/delete 同理)。
(5) 释放了内存却继续使用它。
常见的三种情况:
① 程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
② 函数的return 语句写错了,注意不要返回指向 “栈内存” 的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
③ 使用free 或delete 释放了内存后,没有将指针设置为NULL。导致产生 “野指针”。
3. 计算内存容量
用运算符 sizeof 可一计算出数组的容量(字节数)。
例如:
char a[] = "hello world"; // sizeof(a) = 12
char *p = a; // sizeof(p) = 4 相当于 sizeof(char*)
数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
例如:
void Func(char a[100]){
cout<< sizeof(a) << endl; // 4 字节而不是100 字节
}
4. 指针参数如何传递内存
如果用函数去申请内存可采取下面两种方式:
void getMemory(char **p, int num){ // 指向指针的指针 *p = (char*)malloc(sizeof(char) * num); }
调用时: GetMemory(&pstr, 100);
char *getMemory2(int num){ char *p = (char *)malloc(sizeof(char) * num); return p; }
下面这种方式将会造成内存泄漏
void GetMemory(char *p, int num){ p = (char *)malloc(sizeof(char) * num); }
不要用return 语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡
char *GetString(void){ char p[] = "hello world"; return p; // 编译器将提出警告 }
5. 防止 “野指针” 的出现
“野指针”不是NULL 指针,是指向“垃圾”内存的指针,形成原因有下面几种:
(1) 指针变量没有被初始化。
任何指针变量刚被创建时不会自动成为NULL 指针,它
的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么
将指针设置为NULL,要么让它指向合法的内存。例如
char *p = NULL;
char *str = (char *) malloc(100);
(2)指针p 被free 或者delete 之后,没有置为NULL,让人误以为p 是个合法的指针。
(3)指针操作超越了变量的作用范围。
例如:
class A{ public: void Func(void){ cout << “Func of class A” << endl; } }; void Test(void){ A *p;{ A a; p = &a; // 注意 a 的生命期 } p->Func(); // p 是“野指针” }
6. malloc/free 和 new/delete 的区别
malloc 与free 是C++/C 语言的标准库函数,new/delete 是C++的运算符。它们都可用于申请动态内存和释放内存。
对于非内部数据类型 new 在分配内存的同时会调用构造函数,delete 在回收内存时会掉用析构函数,而 malloc/free则没有此功能。
7. 如何面对内存耗尽的情况。
通常有三种处理方式:
(1)判断指针是否为NULL,如果是则马上用return 语句终止本函数。
例如:
void Func(void){ A *a = new A; if(a == NULL){ return; } … }
(2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。
例如:
void Func(void){ A *a = new A; if(a == NULL){ cout << “Memory Exhausted” << endl; exit(1); } … }
(3)为new 和malloc 设置异常处理函数。例如Visual C++可以用_set_new_hander 函数为 new 设置用户自己定
义的异常处理函数,也可以让malloc 享用与new 相同的异常处理函数。
7. malloc/free 的使用的注意点
函数 malloc 的原型如下:
void * malloc(size_t size);
用 malloc 申请一块长度为length 的整数类型的内存,程序如下:
int *p = (int *) malloc(sizeof(int) * length);
(1) malloc 返回值的类型是void *,所以在调用malloc 时要显式地进行类型转换,将 void * 转换成所需要的指针类型。
(2) malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我们通常记不住int, float 等数据类
型的变量的确切字节数。例如int 变量在16 位系统下是2 个字节,在32 位下是4 个字节;而float 变量在16 位系统下是
4 个字节,在32 位下也是4 个字节。
函数 free 的原型如下:
void free( void * memblock );
对于语句 free(p)如果p 是NULL 指针,那么free 对p 无论操作多少次都不会出问题。
如果p 不是NULL 指针,那么 free 对 p 连续操作两次就会导致程序运行错误。
8. new/delete 的使用的注意点
new 内置了sizeof、类型转换和类型安全检查功能。
申请内存时只要
int *p2 = new int[length];
delete p; // 释放内存
delete []p2; // p2 是一段连续的存储区