您的位置:首页 > 编程语言 > C语言/C++

解读林锐-高质量C,C++编程指南

2015-02-28 14:38 309 查看

内存管理

序言:一个面试题(指针能否作为动态申请内存的传入参数?)引发的书籍阅读。—昨天(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个对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: