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

高质量C++编程指南学习笔记第7章---thanks to林锐

2013-02-04 18:18 423 查看
第七章     内存管理
主线是分配、使用、释放。分配的话,分配多少合适,有多少可用,分配失败怎么处理。
7、1内存的分配方式
三种分配内存的方式:
1) 
从静态存储区域分配。内存在编译时已分配好,这块内存在程序整个运行期间都存在。如全局变量,static变量。
2) 
在栈上创建。栈内存分配运算内置于处理器的指令集中,效率很高,但分配的内存容量有限。
3) 
从堆上分配,亦称动态分配。运行时用new或malloc申请任意多的内存,程序员自己负责在何时free或delete内存。
7、2常见的内存错误及其对策
常见的内存错误及其对策:
1) 
内存分配未成功,却是用了它。常用解决方式是,在使用前检查指针是否为NULL。若指针p是函数的参数,则在函数入口处用assert(p!=NULL)进行检查。若用malloc或new来申请内存,应该用if
(p == NULL)或if (p != NULL)进行放错处理。
2) 
内存分配虽成功,但尚未初始化就使用它。因内存的缺省初始值究竟是什么没有统一的标识,所以无论何种方式创建数组,都别忘了赋初值,即使是赋零值也不可省略。
3) 
内存分配成功且已初始化,但操作越过了内存的边界。
4) 
忘记释放内存,造成内存泄露。动态内存的申请与释放必须配对,否则肯定有错。
5) 
释放了内存却继续使用它。有三种情况:
Ø 
程序中对象调用关系过于复杂,搞不清某个对象究竟是否已释放了内存,此时该重新设计数据结构,从根本上解决对象管理的混乱局面。
Ø 
函数的return写错了,主要不要返回指向“栈内存”的指针或引用。如
char * Func(void)

{

char str[] = “hello world”; // str的内存位于栈上



return str; // 该内存在函数结束时被自动销毁,所以将导致错误

}

Ø 
使用free或delete释放内存后,没有将指针设置为NULL。导致产生“野指针”。

7、3指针与数组的对比
        
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命周期内保持不变,只有数组的内容可变。你可能好奇,难道不能在堆上创建数组吗?int
*a=new int[3];a难道不是在堆上吗?可是a是数组吗?a是一个指针。也许还有的说指针数组,int *p [10];可它再堆上吗?复习下指针数组和数组指针的区别:加“的”,加括号的为指针。
        
下面比较下指针与数组的特性:
1) 
修改内容。字符数组的内容可以改变。若指针p指向常量字符串”world”,常量字符串的内容是不可以被改变的。p[0]=’x’错误。
2) 
内容复制与比较。不能对数组名进行直接复制与比较。若想把数组a的内容复制给数组b,不能用b = a,该用标准库函数strcpy进行复制;比较时用strcmp。
p = a只是把a的地址赋给了p,若想复制内容,可先用malloc为p申请一块容量为strlen(a)
+1个字符的内存,再用strcpy进行字符串复制。
3) 
计算内存的容量。sizeof可计算出数组的容量。C/C++没法知道指针所指向的内存容量,除非在申请内存时记住它。注意当数组作为函数参数传递时,该数组自动退化为同类型的指针。
char *a = “hello world”;
cout<<sizeof(a)<<endl;              //12字节
void Func(char a[100])
{
    cout<<sizeof(a)<<endl;     //4字节而不是100字节
}
7、4指针参数是如何传递内存的
1)若函数的参数是一个指针,不要指望用该指针去申请动态内存。
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"); // 运行错误

}

str并没有获得期望的内存,str依旧是NULL。毛病出在GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是_p,编译器使_p
= p。若函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可作为输出参数的原因。本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但p丝毫未变。所以函数GetMemory不能输出任何东西。
2)若非要用指针参数去申请内存,该用“指向指针的指针”。
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);

}

3)因“指向指针的指针”不易理解,可用函数返回值来传递动态内存。
char *GetMemory3(int num)

{

char *p = (char *)malloc(sizeof(char) * num);

return p;

}

void Test3(void)

{

char *str = NULL;

str = GetMemory3(100);

strcpy(str, "hello");

cout<< str << endl;

free(str);

}

4) 
上述方法虽好用,但常被用错。这里强调不要return返回指向“栈内存”的指针。
char *GetString(void)

{

char p[] = "hello world";

return p; // 编译器将提出警告

}

void Test4(void)

{

char *str = NULL;

str = GetString(); // str 的内容是垃圾

cout<< str << endl;

}

5)
char *GetString2(void)

{

char *p = "hello world";

return p;

}

void Test5(void)

{

char *str = NULL;

str = GetString2();

cout<< str << endl;

}

虽然运行正确,但GetString2的设计概念是错误的。因GetString2内的"hello world"是常量字符串,位于静态存储区,在生命周期内恒定不变。无论何时调用GetString2,返回的都是一个“只读”的内存块。
7、5free和delete把指针怎么啦
        
它们只是把指针所指向的内存释放掉,并没有把指针本身干掉。指针p被free后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p成了“野指针”。所以在释放掉内存后,记得将p设置为NULL,下次使用时可通过if
(p! = NULL)来进行放错处理。
7、6动态内存会自动释放吗
        
函数体内的局部变量在函数结束时自动消亡。不要误以为局部指针变量p所指向的动态内存也会随函数结束而释放。
两个特征:
        
指针消亡了,并不表示所指的内存会被自动释放。
        
内存释放了,并不表示指针会消亡或成了NULL指针。
7、7杜绝“野指针”
        
“野指针”不是NULL指针,是指向垃圾内存的指针。“野指针”的成因主要有:
Ø 
指针变量没有初始化。
Ø 
指针p被free或delete后,没有置为NULL,让人误以为p是个合法的指针。
Ø 
指针操作超越了变量的作用范围。这种情况让人防不胜防。如
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是“野指针”

}执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。有点编译器可能会将这个优化,不报错。
7、8有了malloc/free为什么还要new/delete
         malloc/free是C/C++的标准库函数,new/delete是C++的运算符。先说下标准库函数和运算符的区别:对非内部数据类型的对象而言,对象在创建的同时要自动执行构造函数,对象在消亡之前要执行析构函数。malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能把执行构造函数和析构函数的任务强加于malloc/free。C++运算符new/delete可以完成动态内存分配和初始化以及完成清理与释放内存工作。对内部数据类型而言,这两个函数无差别。
7、9内存耗尽怎么办
内存耗尽时,最好调用exit(1)直接退出程序,用return的话,若多处申请动态内存,就需要释放每个内存,太麻烦。
7、10malloc/free的使用要点
        
应当把注意力集中在两个要素上:“类型转换”和“sizeof”
Ø 
malloc返回值的类型是void*,所以在使用malloc时要显示进行类型转换,将void*转换成所需的指针类型。
Ø 
malloc只关心内存的总字节数,所以申请时注意使用sizeof。
例子:用malloc申请一块长度为length的整数类型的内存:
int *p = (int*)malloc(sizeof(int)*length);
free:若p是NULL指针,则free对p操作多少次都不会有问题;若p不是NULL指针,free对p连续操作两次就会导致程序运行错误。
7、11new/delete的使用要点
         new内置了sizeof、类型转换和类型安全检查功能。若用new创建对象数组,只能使用对象的无惨构造函数。Obj *objects
= new Obj[100];在使用delete释放对象数组时,应这样:delete []objects;若写为delete objects;相当于delete objects[0],漏掉了另外99个对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: