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

第七章     内存管理

主线是分配、使用、释放。分配的话,分配多少合适,有多少可用,分配失败怎么处理。

71内存的分配方式

三种分配内存的方式:

1)  从静态存储区域分配。内存在编译时已分配好,这块内存在程序整个运行期间都存在。如全局变量,static变量。

2)  在栈上创建。栈内存分配运算内置于处理器的指令集中,效率很高,但分配的内存容量有限。

3)  从堆上分配,亦称动态分配。运行时用newmalloc申请任意多的内存,程序员自己负责在何时freedelete内存。

72常见的内存错误及其对策

常见的内存错误及其对策:

1)  内存分配未成功,却是用了它。常用解决方式是,在使用前检查指针是否为NULL。若指针p是函数的参数,则在函数入口处用assert(p!=NULL)进行检查。若用mallocnew来申请内存,应该用if (p == NULL)if (p != NULL)进行放错处理。

2)  内存分配虽成功,但尚未初始化就使用它。因内存的缺省初始值究竟是什么没有统一的标识,所以无论何种方式创建数组,都别忘了赋初值,即使是赋零值也不可省略。

3)  内存分配成功且已初始化,但操作越过了内存的边界。

4)  忘记释放内存,造成内存泄露。动态内存的申请与释放必须配对,否则肯定有错。

5)  释放了内存却继续使用它。有三种情况:

Ø  程序中对象调用关系过于复杂,搞不清某个对象究竟是否已释放了内存,此时该重新设计数据结构,从根本上解决对象管理的混乱局面。

Ø  函数的return写错了,主要不要返回指向“栈内存”的指针或引用。如

char * Func(void)

{

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

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

}

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

73指针与数组的对比

         数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命周期内保持不变,只有数组的内容可变。你可能好奇,难道不能在堆上创建数组吗?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,若想复制内容,可先用mallocp申请一块容量为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字节

}

74指针参数是如何传递内存的

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,返回的都是一个“只读”的内存块。

75freedelete把指针怎么啦

         它们只是把指针所指向的内存释放掉,并没有把指针本身干掉。指针pfree后其地址仍然不变(NULL),只是该地址对应的内存是垃圾,p成了“野指针”。所以在释放掉内存后,记得将p设置为NULL,下次使用时可通过if (p! = NULL)来进行放错处理。

76动态内存会自动释放吗

         函数体内的局部变量在函数结束时自动消亡。不要误以为局部指针变量p所指向的动态内存也会随函数结束而释放。

两个特征:

         指针消亡了,并不表示所指的内存会被自动释放。

         内存释放了,并不表示指针会消亡或成了NULL指针。

77杜绝“野指针”

         “野指针”不是NULL指针,是指向垃圾内存的指针。“野指针”的成因主要有:

Ø  指针变量没有初始化。

Ø  指针pfreedelete后,没有置为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就成了“野指针”。有点编译器可能会将这个优化,不报错。

78有了malloc/free为什么还要new/delete

         malloc/freeC/C++的标准库函数,new/deleteC++的运算符。先说下标准库函数和运算符的区别:对非内部数据类型的对象而言,对象在创建的同时要自动执行构造函数,对象在消亡之前要执行析构函数。malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能把执行构造函数和析构函数的任务强加于malloc/freeC++运算符new/delete可以完成动态内存分配和初始化以及完成清理与释放内存工作。对内部数据类型而言,这两个函数无差别。

79内存耗尽怎么办

内存耗尽时,最好调用exit(1)直接退出程序,用return的话,若多处申请动态内存,就需要释放每个内存,太麻烦。

710malloc/free的使用要点

         应当把注意力集中在两个要素上:“类型转换”和“sizeof

Ø  malloc返回值的类型是void*,所以在使用malloc时要显示进行类型转换,将void*转换成所需的指针类型。

Ø  malloc只关心内存的总字节数,所以申请时注意使用sizeof

例子:用malloc申请一块长度为length的整数类型的内存:

int *p = (int*)malloc(sizeof(int)*length);

free:若pNULL指针,则freep操作多少次都不会有问题;若p不是NULL指针,freep连续操作两次就会导致程序运行错误。

711new/delete的使用要点

         new内置了sizeof、类型转换和类型安全检查功能。若用new创建对象数组,只能使用对象的无惨构造函数。Obj *objects = new Obj[100];在使用delete释放对象数组时,应这样:delete []objects;若写为delete objects;相当于delete objects[0],漏掉了另外99个对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值