Chinaunix首页 | 论坛 | 博客
  • 博客访问: 156310
  • 博文数量: 36
  • 博客积分: 802
  • 博客等级: 准尉
  • 技术积分: 717
  • 用 户 组: 普通用户
  • 注册时间: 2012-06-02 22:47
文章分类
文章存档

2012年(36)

分类: C/C++

2012-09-28 15:02:38

C++内存分为5个区域,他们分别是堆、栈、自由存储区、静态/全局存储区、常量存储区:
(1)栈:在执行函数时候,函数内部变量的存储空间都可以在栈上创建,函数执行结束时候,这些存储单元自动的被释放,栈内部分配运算内置处理器的指令集中,效率高,但是内存分配内存容量有限。
(2)堆:就是那些由new分配的内存块,他们的释放,编译器不去管理,由我们程序员的程序控制,一般一个new对应一个delete,如果程序员没有释放,则等到程序结束,操作系统会释放它。
(3)自由存储区:就是那些由malloc分配的内存块,它和堆非常相似,不过需要free来释放它。
(4)全局/静态变量区:全局变量和静态变量都被分配到同一块内存中。
(5)常量存储区:这是一块比较特殊的存储区,不允许被改变。
区分堆与栈

点击(此处)折叠或打开

  1. void func()
  2. {
  3.    int *p=new int[5];
  4. }
这句话中:new分配了一个堆内存(new分配堆内存),那么指针P呢?p分配了一个栈内存(因为它是一个局部变量)--->在栈内存中存放一个指向一块堆内存的指针P。
堆与栈的区别:
(1)管理方式不同:对于栈来说,由编译器自动控制,而对于堆来说,释放工作要程序员自己做。
(2)空间大小不同:对于堆来讲,在32位系统下,一个堆内存差不多有4G大小,对于栈来讲,一般只有很小的空间(大约1M)
(3)能否产生碎片问题:对于堆来讲,由于不停的new/delete容易产生很多碎片,使得程序的运行速率降低,而对于栈来讲,它都是先进后出,有一定的顺序,一一对应。
(4)生长方向:对于堆来讲,生长方向向上,随着地址增大的方向生长。对于栈来讲,生长方向向下,随着地址减小的方向生长。
(5)分配方式:堆都是动态分配,栈既有动态分配又有静态分配
(6)分配效率:栈的分配效率比堆高很多
常见的内存错误及其对策
*内存分配未成功,用户却使用了。
解决办法:在使用前用if语句判断。
*内存虽然分配成功,却未进行初始化。//别忘了初始化
1:没有初始化的观念。
2:默认为系统会将其自动初始化为0。
*内存分配了,也初始化了,但是越界
例如:for循环中,对于数组下标的“多1少1”
*忘记释放内存
调用一次,内存丢掉一块,到最后内存耗尽。
*释放了内存却在使用它
(1)函数的return语句,注意不要返回“栈内存”的“指针”或者“引用”,因为该内存在函数结束的时候被释放
(2)使用free或者delete语句后,未将其赋值为NULL,导致产生“野指针”
【规则一】用malloc和new申请成功后,应该立即检查指针值是否为NULL,防止指针值为NULL
【规则二】不要忘记为数组和动态内存赋初值,防止将未被初始化的内存作为右值使用
【规则三】避免数组与指针下标越界,特别当心“多1”,“少1”问题
【规则四】动态内存的申请与释放,必须配对,防止内存泄漏(即:一个malloc对应一个delete,一个new对应一个delete)
【规则五】用free或者delete释放内存后,立即将指针设置为NULL,防止产生“野指针
指针与数组
        数组要么在静态存储区被创建,要么在栈区被创建,数组名对应(而不是指向)一块内存,其容量在生命期内保持不变,只有数组的内容可以改变。
        指针可以随时指向任意类型的内存块,它的特征是“可变”,我们常用指针操作动态内存。
1:修改内容

点击(此处)折叠或打开

  1. char a[]="hello";
  2. a[0]='X'//数组内容可以改变
  3. cout<<a<<endl;
  4. char *p="world";//p指向常量字符串
  5. cout<<p<<endl;//编译器不能发现该错误,但是,常量字符串的内容不能被修改。

  6. -->实际输出
  7. Xello
  8. 段错误
2:计算内存容量

点击(此处)折叠或打开

  1. char a[]="hello world";
  2. char *p=a;
  3. cout<<sizeof(a)<<endl;//12个字节
  4. cout<<sizeof(p)<<endl;//4个字节
sizeof可以计算数组容量,如下所示:
sizeof(a)=12,指针p指向a,但是sizeof(p)=4;这是因为sizeof(p)得到的是一个指针变量字节数,相当与sizeof(char *),而不是p所指的内存容量。(C/C++语言没有办法知道指针所指内存容量,除非申请的时候就记住
指针参数如何传递内存
不要指望用该指针去申请动态内存

点击(此处)折叠或打开

  1. void GetMemory(char *p,int num)
  2. {
  3.      p=(char *)malloc(sizeof(char)*num);
  4. }
  5. void Test(void)
  6. {
  7.      char *str=NULL;
  8.      GetMemory(str,100);//str仍然等于NULL
  9.      strcpy(str,"hello");//运行错误
  10. }
比如这个例子,编译器总是为了函数的每个参数制作临时的副本,指针参数p的副本_p,_p=p,如果函数体内的程序修改了_p的值,就导致参数p的内容做相应的改变。这就是指针可以用作输出函数的原因。
但是在本例子中,_p申请了新的内存,只是把_p所指向的内存地址改变了,p丝毫没有改变,所以函数GetMemory()并不能输出任何东西。事实上,每执行一次GetMemory()就会泄漏一部分内存,所以必须free掉。
修改一:指向指针的指针

点击(此处)折叠或打开

  1. void GetMemory(char **p,int num)
  2. {
  3.       *p=(char *)malloc(sizeof(char)*num);
  4. }

  5. void Test()
  6. {
  7.      char *str=NULL;
  8.      GetMemory(&str,100);
  9.      strcpy(str,"hello");
  10.      free(str);
  11. }
修改二:函数返回

点击(此处)折叠或打开

  1. char *GetMemory(int num)
  2. {
  3.      char *p=(char *)malloc(sizeof(char));
  4.      return p;
  5. }

  6. void Test()
  7. {
  8.       char *str=NULL;
  9.       str=GetMemory(str,100);
  10.       strcpy(str,"hello");
  11.       printf("%s\n",str);
  12.       free(str);
  13. }
-------------------用函数返回当然好,但是也有人老用错return语句,这里需要强调“栈内存”的指针,因为该内存在函数结束的时候,自动消亡,所以不能返回。
错误一:return语句使用错误

点击(此处)折叠或打开

  1. char *GetMemory()
  2. {
  3.       char p[]="hello world";//p为一个数组,分配在“栈内存上”,所以在该函数结束时候,自动消亡
  4.       return p;//出错
  5. }
错误二:常量字符串,生命期内恒定不变

点击(此处)折叠或打开

  1. char *GetMemory()
  2. {
  3.    char *p="hello world";
  4.    return p;
  5. }
运行不会出错,但是函数GetMemory设计概念上是有错的,因为GetMemory内的“hello world”是常量字符串,位于静态存储区,它的生命期内恒定不变,无论什么时候,调用GetMemory,它返回的始终是一个“只读”的内存块。
野指针:
也只真不是NULL指针,是指向“垃圾”内存的指针。
野指针形成原因:
(1)指针变量没有被初始化,任何指针变量刚被创建的时候不会自动形成NULL指针,缺省值是随机的,它会乱指,指针变量在创建的同时,应该被初始化为NULL。

点击(此处)折叠或打开

  1. char *p=NULL;
  2. p=(char *)malloc(sizeof(char)*num);
(2)指针P被free或者delete之后,没有设置为NULL,让人以为p是一个合法指针
(3)指针操作超越了变量的作用域范围

点击(此处)折叠或打开

  1. class A
  2. {
  3.   public:
  4.         void Func()
  5.              {
  6.                cout<<"Func of class A"<<endl;
  7.              }
  8. }

  9. void Test()
  10. {
  11.    A *p;
  12.    {
  13.        A a;
  14.        p=&a;//a为栈内存上的变量,在A *p()结束后,就释放了
  15.    }
  16.   p->Func();//所以p为“野指针”
  17. }
有了malloc/free为什么还需要new/delete?
         对于非内部数据类型的对象而言,光用mallo/free无法满足动态对象的要求,对象在创建的时候要自动执行构造函数,在对象消亡之前要自动执行析构函数。

         由于malloc/free是库函数而不是运算符,不在编译器控制范围内,不能够把执行析构函数和构造函数的任务加于mallo/free(new/delete可以自动执行构造函数/析构函数,但是malloc/free不行。new/delete是运算符,而malloc/free是函数)

内存耗尽?

         如果在申请动态内存的时候找不到足够大的内存块,malloc与new将返回NULL指针,宣告失败。
<1>判断指针是否为NULL,是则return终止函数

点击(此处)折叠或打开

  1. void Func(void)
  2. {
  3.       A *a=new A;
  4.       if(a==NULL)
  5.          return;
  6. }
<2>判断是否为NULL,立即exit

点击(此处)折叠或打开

  1. void Func(void)
  2. {
  3.      A *a=new A;
  4.      if(a==NULL)
  5.         exit(1);
  6. }
new/delete?

new内置了sizeof函数,类型函数转换,安全检查功能。new在创建动态对象的同时完成了初始化工作,如果对象有多个构造函数,那么new的语句也可以有多种形式

obj *objects=new obj[100];//创建100个动态对象,不能赋初值
delete [] objects;//不能忘记  [] ,释放对象数组。











阅读(2231) | 评论(1) | 转发(1) |
给主人留下些什么吧!~~

lmnos2012-10-03 18:40:15

写的好,学习了。