解读林锐-高质量C,C++编程指南
来源:互联网 发布:js窗口大小改变事件 编辑:程序博客网 时间:2024/05/17 06:02
内存管理
序言:一个面试题(指针能否作为动态申请内存的传入参数?)引发的书籍阅读。—昨天(2015.2.27)看了一下午林锐的C、C++关于内存管理的内容,有点豁然开朗的感觉。今天下午抽出一个半小时的时间进行总结顺便再温故知新。
伟大的 Bill Gates 曾经失言:
640K ought to be enough for everybody
- - - - - - - - - - - - - - - - – - – – - – - - - - - -Bill Gates 1981
内存分配方式
- 静态存储区域分配。内存在程序编译的时候就已经分配好了,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
- 在栈上构建。在执行函数时,函数内局部变量的存储单元都可以在栈上构建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 堆上分配,也称为动态内存分配。程序在运行的时候用mallo或new申请任意多少的内存,程序员自己负责何时用free或者delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但是问题也是最多。
内存使用规则
- 用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
- 不要忘记为数组或动态内存赋初值。防止将未被初始化的内存作为右值使用。
- 避免数组或指针的下表越界。
- 动态内存的申请与释放必须配对,malloc对应free,new对应delete,防止内存泄露。
- 用free或者delete释放内存之后,立即将指针设置为NULL,防止产生”野指针“。
数组与指针
绪言:程序中,指针和数组在不少地方可以相互交替换着使用,让人产生认为两者是等价的。
数组:要么在静态存储区域被创建(全局数组),要么在栈上被创建。数组对应着(不是指向)一块内存,其地址与容量在生命期间内保持不变,只有数组的内容可以改变。
指针:可以随意指向任意类型的内存块,它的特征是“可变”,经常使用指针来操作动态内存,指针比数组灵活,但是也更加危险。
如果函数的参数是一个指针,不要期望使用该指针去申请动态内存。
void GetMemory(char *p, int num) { p = (char*)malloc(sizeof(char) * num); } void Test(void){ char *str = NULL; GetMemory(str, 100); // str 仍然为 NULL strcpy(str, "hello"); // 运行错误 }
毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。
如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”。
void GetMemory2(char **p, int num) { *p =(char*)malloc(sizeof(char) * num); } void Test2(void) { char *str = NULL; GetMemory2(&str, 100); // 注意参数是 &str,而不是str strcpy(str, "hello"); cout<< str << endl; free(str); }
Free和Delete对指针的操作
Free和Delete的作用只是把指针所指的内存给释放掉,但并没有把指针本身删掉。
char *p = (char *)malloc(100);strcpy(p,"elvis");free(p); //所指内存释放,p所指的地址依然不变if(p != NULL) //出错{ strcpy(p,"xinxin"); }
上诉代码的if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。
问题:动态内存会被自动释放吗?
函数体内的局部变量在函数结束时自动消亡。很多人误以为下面程序是正确的。理 由是p是局部的指针变量,它消亡的时候会让它所指的动态内存一起完蛋。这是错觉!
void Func(void) { char *p = (char *) malloc(100); // 动态内存会自动释放吗? }
上述程序试图让动态内存自动释放 我们发现指针有一些“似是而非”的特征:
(1)指针消亡了,并不表示它所指的内存会被自动释放。
(2)内存被释放了,并不表示指针会消亡或者成了NULL指针。
这表明释放内存并不是一件可以草率对待的事。也许有人不服气,一定要找出可以草率行事的理由: 如果程序终止了运行,一切指针都会消亡,动态内存会被操作系统回收。既然如此在程序临终前,就可以不必释放内存、不必将指针设置为NULL了。终于可以偷懒而不会发生错误了吧?
如果别人把那段程序取出来用到其它地方怎么办?
问题:有了malloc/free为什么还要new/delete?
malloc 与 free是c/c++语言的标准库函数,new/delete是c++的运算符。他们都用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数为不是运算符,不再编译器控制权限之内,不能够把执行构造函数和析构函数的任务加于malloc/free.
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理和释放内存工作的运算符delete。注意new/delete不是库函数。
从一个例子来看下malloc/free和new/delete如何实现对象的动态内存管理。
class Obj{ public : Obj(void){ cout << “Initialization” << endl; } ~Obj(void){ cout << “Destroy” << endl; } void Initialize(void){ cout << “Initialization” << endl; } void Destroy(void){ cout << “Destroy” << endl; } }; void UseMallocFree(void){ Obj *a = (obj *)malloc(sizeof(obj)); // 申请动态内存 a->Initialize(); // 初始化 //… a->Destroy(); // 清除工作 free(a); // 释放内存 } void UseNewDelete(void) { Obj *a = new Obj; // 申请动态内存并且初始化 //… delete a; // 清除并且释放内存 }
Initialize函数模拟了构造函数的功能,函数Destroy模拟了析构函数的功能。函数UseMallocFree中,由于malloc/free不能执行构造函数和析构函数,必须调用成员函数Initialize和Destroy来完成初始化与清除工作。函数UseNewDelete则简单得多。
所以我们不要企图用malloc/free来完成动态内存对象的内存管理,应该用new /delete。由于内部数据类型的“对象”没有构造和析构的过程,所以对它们而言malloc/free和new /delete是等价的。
虽然 new/delete的功能已经完全覆盖了malloc/free,C++ 依然保留着malloc/free函数,是因为C++程序经常要调用到C函数,而C程序只能用malloc/free管理动态内存。
如何处理内存耗尽
如果申请动态内存是找不到足够大的内存块,malloc / new 将返回NULL指针。 三种方法处理“内存耗尽”问题。
1.判断指针是否为NULL,如果是马上用return语句终止本函数。
这里要特别注意: return 语句是终止本函数的执行。
2.判断指针是否为NULL,如果是马上用exit(1)来终止整个程序的运行。
特别提示:exit(1) 用来终止整个程序的运行。重点内容**
* malloc/free VS new /delete *
malloc 的原型声明:
void * malloc(size_t size);
注意:malloc返回的是 void * 类型的指针。使用的时候要显式的类型转换。
free 的原型声明:
void free (void * memblock);
为什么free函数不象malloc函数那样复杂呢?这是因为指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p是NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。
new 与delete的使用
int * p1 = (int )malloc(sizeof(int) length);
int * p2 = new int[length];
这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。
经典示例:
对象数组的创建Obj *obj1 = new Obj[100];对象数组的释放delete []obj1; //其他写法出错delete obj1; //错误用法相当于delete obj1[0],漏掉了后面的99个对象。
- 解读林锐-高质量C,C++编程指南
- 高质量C编程指南
- 高质量C++/C编程指南(参考文献)
- 高质量C++/C编程指南
- 高质量C++/C编程指南
- 高质量C++/C 编程指南
- 读《高质量C++/C编程指南》
- 高质量C++/C编程指南
- 高质量C++/C编程指南
- 高质量C++/C编程指南
- 高质量C++/C编程指南 -- 前言
- 高质量C++/C编程指南
- 高质量C++/C编程指南
- 高质量C++/C编程指南 -- 前言
- 高质量C++/C编程指南
- 高质量C++/C编程指南
- 高质量C++/C编程指南[1]
- 高质量C++/C编程指南[2]
- CNN基础及开发环境搭建(综合参考)
- 【c#源码】基于TCP通信的客户端断线重连
- vim 常用配置
- 自定义控件学习第一课
- UVA 1204 Fun Game(状压dp)
- 解读林锐-高质量C,C++编程指南
- 计算几何--两圆的位置关系(求交点个数及交点坐标)
- iOS CGContextRef画图小结
- Hibernate——以面向对象的思维操作关系数据库(一)
- OC里的继承和重写
- oracle中获取系统时间
- 【头脑风暴】要创建一家会被科技巨头收购的公司,你需要怎么做?
- Makefile
- php和html混编的三种方式