C++ 堆

堆的理解

堆是一大堆不连续的内存区域,在系统中由列表将他们串接起来,为了数据隐蔽,堆中的每个单元内存都是匿名的,因此你必须先在堆中申请一个内存单元地址,然后把他保存在一个指针中,这样你只有使用指针才可以访问到该内存单元的内容。

使用堆的好处

可以存储比较大的数据
程序员自己控制创建和释放
只有特定的指针才能访问特定的数据,这样就避免了任何试图修改他的操作

使用方法

首先创建一个堆,然后定义一个指向该堆的指针,这样就只能通过该指针才能访问堆中的数据 C++中使用关键字new创建一个堆并分配一个内存,在 new 后面跟一个要分配的对象类型,编译器根据这个类型来确定分配内存的大小

int *p;//定义了一个指向整型的指针变量p
p=new int;//new来在堆中创建一个int类型的内存区域,然后将该区域的内存指针赋值给指针变量 p,这样的话p所指向的就是这块新建立的内存区域  
new int //创建成功了返回一个地址 把这个地址赋给p

也可以合并为一句话 int *p=new int;这样的话定义指针p的同时初始化了他的值为一个在堆中新建的int型的存储区的内存地址,我们可以像使用普通指针一样使用他并且把值赋给他所指向的内存空间*p=1;注意由于内存很有限所以可能会出现没有足够内存而无法满足new请求的情况,在这种情况下,new会返回0,该值被赋值给指针后该指针就是一个空指针,空指针不会指向有效数据.

注意指针的内存泄露

由于使用new创建的内存空间不会被系统自动释放,因此假如你不去释放他那么该区域的内存将始终不能为其他数据所使用,而指向该内存的指针是个局部变量,当定义该指针的函数结束并返回的时候,指针也就消失了,那么我们就再也找不到该块内存区域了,这种情况我们叫内存泄露,因此假如你不需要一块内存空间 那么就必须对指向他的指针使用关键字deleteint *p=new int; delete p;这样的话将释放指针所指向的内存,而不会释放指针,因此你还可以使用这个指针而先前指针指向的堆中的内存已经释放了。如果指针指向了堆中对象的话只能释放一次不能重复释放,要释放指针的话将p=0;这样的话 释放一个空指针是可以的。

int *p=new int;//向系统申请一块int类型的堆中的内存把返回的地址赋给int 类型的指针p
delete p;//删除了指针p所指向的那块内存但是指针还保留着
p=new int;//再申请一块堆中内存把返回的地址赋值给指针p
delete p;//再删除指针所指向的那块内存
p=0;//设置指针为空指针
delete p;//删除指针 如果没有上面的设置为空指针那么俩个delete p是错误的
return 0;

内存泄露的小代码:

int *p=new int;
p=new int;

正确的做法:

in*p=new int;
delete p;
p=new int;

或者

in*p1=new int;
in*p2=new int;

堆代码解释

Human *p;//定义了一个Human类的指针
p=new Human;//创建了一块内存区域,同时调用Human类的默认构造方法来构造一个对象,它所占用的内存大小根据Human对象的成员变量来决定,假如该类有俩个int类型的成员变量.那么该对象占用2*4=8个字节,本行的右半部分创建一个对象完毕后,跟着将该内存地址赋值给左边的指针变量p.
上面俩行可以合并 Human *p=new Human;
delete p;//调用对象的析构函数 并释放内存

堆和栈的区别

一般认为在c中分为这几个存储区

  • 栈 - 有编译器自动分配释放
  • 堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收
  • 全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,全局变量会在程序结束后释放。
  • 另外还有一个专门放常量的地方,也会在程序结束后释放。

在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函数中的"adgfdf"这样的字符串存放在常量区。比如:

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

还有就是函数调用时会在栈上有一系列的保留现场及传递参数的操作。栈的空间大小有限定,vc的缺省是2M。栈不够用的情况一般是程序中分配了大量数组和递归函数层次太深。有一点必须知道,当一个函数调用完返回后它会释放该函数中所有的栈空间。栈是由编译器自动管理的,不用你操心。堆是动态分配内存的,并且你可以分配使用很大的内存。但是用不好会产生内存泄漏。并且频繁地malloc和free会产生内存碎片(有点类似磁盘碎片),因为c分配动态内存时是寻找匹配的内存的。而用栈则不会产生碎片。在栈上存取数据比通过指针在堆上存取数据快些。一般大家说的堆栈和栈是一样的,就是栈(stack),而说堆时才是堆heap.栈是先入后出的,一般是由高地址向低地址生长。

堆(heap)和栈(stack)是C/C++编程不可避免会碰到的两个基本概念。首先,这两个概念都可以在讲数据结构的书中找到,他们都是基本的数据结构,虽然栈更为简单一些。在具体的C/C++编程框架中,这两个概念并不是并行的。对底层机器代码的研究可以揭示,栈是机器系统提供的数据结构,而堆则是C/C++函数库提供的。具体地说,现代计算机(串行执行机制),都直接在代码底层支持栈的数据结构。这体现在,有专门的寄存器指向栈所在的地址,有专门的机器指令完成数据入栈出栈的操作。这种机制的特点是效率高,支持的数据有限,一般是整数,指针,浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构。因为栈的这种特点,对栈的使用在程序中是非常频繁的。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把返回地址推入栈,然后跳转至子程序地址的操作,而子程序中的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。C/C++中的自动变量是直接利用栈的例子,这也就是为什么当函数返回时,该函数的自动变量自动失效的原因。和栈不同,堆的数据结构并不是由系统(无论是机器系统还是操作系统)支持的,而是由函数库提供的。基本的malloc/realloc/free函数维护了一套内部的堆数据结构。当程序使用这些函数去获得新的内存空间时,这套函数首先试图从内部堆中寻找可用的内存空间,如果没有可以使用的内存空间,则试图利用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以适当的形式返回给调用者。当程序释放分配的内存空间时,这片内存空间被返回内部堆结构中,可能会被适当的处理(比如和其他空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。这套复杂的分配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下若干原因:

  • 系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于大量的小内存分类来说会造成浪费。
  • 系统调用申请内存可能是代价昂贵的。系统调用可能涉及用户态和核心态的转换。
  • 没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。

堆和栈的对比

从以上知识可知,栈是系统提供的功能,特点是快速高效,缺点是有限制,数据不灵活;而堆栈是函数库提供的功能,特点是灵活方便,数据适应面广泛,但是效率有一定降低。栈是系统数据结构,对于进程/线程是唯一的;堆是函数库内部数据结构,不一定唯一。不同堆分配的内存无法互相操作。栈空间分静态分配和动态分配两种。静态分配是编译器完成的,比如自动变量(auto)的分配。动态分配由alloca函数完成。栈的动态分配无需释放(是自动的),也就没有释放函数。为可移植的程序起见,栈的动态分配操作是不被鼓励的!堆空间的分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存/释放内存匹配是良好程序的基本要素。可以放一块思考堆和栈的生长方向恰好相反,


|--------------| 低地址
| 堆           |
|--------------|
|      |       |
|      I       |
|              |
|      ^       |
|      栈      | 高地址
-----------------

所以计算机中的堆和栈经常时放一块讲的,一般不是必要就不要动态创建,最讨厌把 new 出来的东西当局部变量用,用万了马上delete 的做法。这么做有以下几个原因:

  • 栈分配比堆快,只需要一条指令就呢给配所有的局部变量
  • 栈不会出现内存碎片
  • 栈对象好管理

当然,某些情况下也要那么写,比如

  • 对象很大
  • 对象需要在某个特定的时刻构造或析够
  • 类只允许对象动态创建,比如VCL的大多数类

当然,必须用堆对象时也不能躲避

posted @ 2016-10-06 21:49  小BIBO  阅读(247)  评论(0)    收藏  举报