高质量C++编程_第7章_内存管理(1)

来源:互联网 发布:mac 代理软件 编辑:程序博客网 时间:2024/05/06 08:43

7.1 内存分配方式

一个C、C++程序编译时内存分为5大存储区:堆区、栈区、全局区、文字常量区、程序代码区。

(1) 在静态存储区域分配

控制者:编译器

      分配时间:在程序编译的时候分配内存

      释放时间:在程序的整个运行期间都存在,程序结束后由OS释放

内容:全局变量,static变量

特点:

      0、速度快,不易出错。

      1、初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和静态变量在另一块区域

       2、定义后,变量的值可以改变

(2) 在栈上创建

      控制者:由编译器自动分配释放

      分配时间:在程序运行(执行函数)的时候分配内存

      释放时间:在函数执行结束时,释放存储单元自动被释放

      举例: 局部变量,函数参数

特点:

     1、栈内存分配运算 内置于处理器的指令集中,分配效率很高,但是分配的内存容量有限

      2、定义后,变量的值可以改变

(3) 从堆上分配

     控制者:程序员一般由程序员分配和释放,。

     分配时间:在程序运行(遇见new或malloc)的时候分配内存。

     释放时间:程序员自己决定,若程序员不释放,程序结束时可能由OS回收,但是程序运行期间不释放的内存属于内存泄露。

举例:使用new 和 malloc申请的空间

     特点:

0、频繁地分配和释放不同大小的堆空间将会产生堆内碎块

    1、程序员使用malloc 或new 申请任意多少的内存,自己负责在何时用free 或delete 释放内存,否则会造成内存泄露

     2、定义后,变量的值可以改变

(4) 文字常量区

   控制着:编译器

分配时间:在程序编译的时候分配内存

   释放时间:程序结束后由系统释放

举例:常量字符串

    特点:定义后,变量的值不可以 改变,只读的

(5) 程序代码区

   内容:存放函数体的二进制代码

举例:

1、存储位置

int a = 0;   全局初始化区 char *p1;    全局未初始化区 main() { int b; 栈 char s[] = "abc"; 栈 char *p2; 栈 char *p3 = "123456"; p3在栈上 , 123456\0在常量区, **而且该字符串定义后不允许改变** static int c =0; 全局(静态)初始化区p1 = (char *)malloc(10); 分配 得来得10和20字节的区域就在堆区。strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 } 

2、内容是否改变

main() { static char b = 'a'; //静态初始化区 b = 'c'; //定义后,值可以修改static char a[] = "hello"; //静态初始化区  a[0] = 'X'; //定义后,值可以修改-------char *p = "world"; // 注意p 指向常量字符串,值不可以改变p[0] = 'X';        //错误,该指针指向的内容不能改变,编译器不能发现该错误,strcpy(p,"123");   //错误,该指针的指向不能改变, 编译器不能发现该错误  } 

 注意:定义并初始化指针的值 是 存在常量区的,指针指向的值 以及 指针的指向 都是不能改变的

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

(1) 内存分配未成功,却使用了它

      解决办法:在使用内存之前检查指针是否为NULL

      举例:如果指针p 是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。

                  如果是用malloc或new来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理。

(2) 内存分配虽然成功,但是尚未初始化就引用它

      解决方法:以无论用何种方式创建数组,都别忘了赋初值,不要使用默认值。即便是赋零值也不可省略,不要嫌麻烦。

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

(4) 忘记了释放内存,造成内存泄露

      解决方法:动态内存的申请与释放必须配对,程序中malloc 与free 的使用次数一定要相同,否则肯定有错误(new/delete 同理)。

                         或者,使用两个变量,分别记录申请空间的数目 和 释放空间的数目。如果二者相等,表示无内存泄露

(5) 释放了内存却继续使用它

      有三种情况:

       (1)  程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,

             解决方法:此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

       (2) 函数的return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”
       (3) 使用free 或delete 释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

             解决方法:释放内存后,主动的把指针赋NULL

7.7 杜绝“野指针”

“野指针”的成因主要有两种:

      (1)  指针变量没有被初始化。

             注意:任何指针变量刚被创建时不会自动成为NULL 指针,它的缺省值是随机的,它会乱指一气

             解决方法:指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

char *p = NULL;char *str = (char *) malloc(100);

(2)  指针p 被free 或者delete 之后,没有置为NULL,让人误以为p 是个合法的指针

            解决方法: 释放指针后直接赋NULL

(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 是“野指针”}

函数 Test 在执行语句p->Func()时,对象a 已经消失,而p 是指向a 的,所以p 就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。

 

原创粉丝点击