欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

堆和栈的区别

程序员文章站 2022-04-21 23:29:52
...

堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式。

是一种常用的树形结构,是一种特殊的完全二叉树,当且仅当满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆。堆的这一特性称之为堆序性。

是一种运算受限的线性表,其限制是指仅允许在表的一端进行插入和删除操作。栈分顺序栈和链式栈两种。使用数组实现的栈叫做顺序栈,使用链表实现的栈叫做链式栈,二者的区别是顺序栈中的元素地址连续,链式栈中的元素地址不连续。

 

虚拟地址空间中堆和栈的位置:

堆和栈的区别

 

堆和栈的主要区别:

(1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,C 语言用 malloc/free进行分配释放,C++用 new/delete 进行分配释放,由于堆需要用户自己管理,因此堆内存很容易造成内存泄露,而栈内存不会。

(2)空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小64bits的Windows默认1M,64bits的Linux默认10M;

(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。

(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。

(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。

(6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内存(eip),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(ebp),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是调用函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。

 

从以上可以看到,堆和栈相比,由于大量malloc()/free()或new/delete的使用,容易造成大量的内存碎片,并且可能引发用户态和核心态的切换,效率较低。栈相比于堆,在程序中应用较为广泛,最常见的是函数的调用过程由栈来实现,函数返回地址、EBP、实参和局部变量都采用栈的方式存放。虽然栈有众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,主要还是用堆。

无论是堆还是栈,在内存使用时都要防止非法越界,越界导致的非法内存访问可能会摧毁程序的堆、栈数据,轻则导致程序运行处于不确定状态,获取不到预期结果,重则导致程序异常崩溃,这些都是我们编程时与内存打交道时应该注意的问题。

 

那么栈和堆的大小,申请一个整形数组最大可以达到多少(linux(不同的内核版本)和windows)

A.栈空间描述

1)x86 32bit linux-redhat 2.6.32-279.el6.i686 gcc-4.4.6

2)win7 64bit 32bit-VS2008

对于栈空间的大小来说,在 linux 系统上是可以通过 ulimit -s 来查看默认的栈大小

的,当然这个栈大小是可以通过命令来设置的,如 linux 系统上:

[aaa@qq.com ~]$ ulimit -s

10240

表示栈的默认大小是 10M,在栈上定义一个 10M 大小的数组,是没有任何问题的,

如:

#define SIZE (1024*1024*10)

char array[SIZE] = {0};

比 10M 大一点的栈数组,也没有问题,比如:

#define SIZE (1024*1024*11)

char array[SIZE] = {0};

再增大一点,如下:

#define SIZE (1024*1024*12)

char array[SIZE] = {0};

编译,运行,结果如下:

[aaa@qq.com test]$ ./a.out

Segmentation fault (core dumped)

结果发生段错误了,那为什么小范围超过 10M 的栈空间也是可以分配的呢?

当我们在代码上分配空间的时候,不管是栈还是堆,其实都只是在虚拟地址空间上分配的内存空间,当真正进行读写使用的时候,随着不断的发生缺页异常,才会去分配真正的物理内存和虚拟地址空间上的页面进行映射。缺页异常处理函数在内核上是 do_page_fault,越界访问栈空间,内核会在一定范围对栈的空间进行增长的。

windows 系统默认的栈大小是 1M,可以通过 VS 编译器来设置程序运行时栈的大小,如下:

解决方法:扩大栈空间的大小,VS 设置项目属性:项目->属性->链接器->系统->堆栈

保留大小

注:这里填的是字节数,如果你想把他扩大为 2M 的话,1024*1024*2 = 2097152

 

B.堆空间描述

堆的大小和当前系统设置的虚拟内存大小息息相关,上面已经描述过,当我们分配内存空间时,没有真正分配过物理内存,只有等到读取指令或者读写数据时产生缺页异常,才会真正分配物理内存和虚拟页面进行映射,当然,物理内存页面在分配的时候,会有页面置换的操作,根据 LRU 最近最久未使用算法,把不经常使用的页面 page out 换出到交换分区当中,以在物理内存中腾出更多的页面给进程使用,因此,程序中可以分配出比物理内存更多的空间,因为有虚拟内存(交换分区)的存在,但是交换分区的大小不宜过大,它毕竟属于磁盘 I/O,读写效率是非常低的,过多的依赖使用 swap 交换分区,会导致系统运行效率哒哒下降,甚至卡顿,所以堆空间的大小和当前系统的物理内存大小,交换分区大小,栈的大小,所使用共享库的大小都息息相关,因为它们都属于用户空间部分,x86 32bit linux 系统默认给用户空间是 3G,看以下实验:

在 linux 系统,可以用以下代码检测一下堆的大小,大约在 2.98G,如下:

以上代码放在 windows 上的 VS 下运行(注意代码运行以后,由于只分配,不释放,系统会卡顿一段时间,无法操作,要有心理准备),这是编译器 32bit vs2008,得到的堆上限大概是 1.85G,因为在 windows 上,4G 空间默认是 2G 给用户空间,2G 给内核空间,所以可分配的堆大小不像 linux 那么大了,而且肯定不会超过 2G。

unsigned int count = 0;
unsigned int block = 1024*1024;
while(1)
 {
  char *p = (char*)malloc(block);
  if(p != NULL)
   {
    count++;continue;
   }
 printf("%0.2f G\n", count*1.0/1024);
 break;
 }

 

那么局部变量存在哪里(栈上)?

x86 体系,32 位 linux 内核给每一个进程都分配 4G 大小的虚拟地址空间,有 3G 的用户态和1G 的内核态,用户态主要存放我们应用程序定义的指令或者数据,局部变量存在于栈上,可看上图,函数运行在栈上开辟了内存,运行完成栈上的内存自动被系统回收。

1.局部变量都在栈上存储

2.全局变量或 static 全局变量都在.data 或者.bss 段存储

3.malloc 或者 new 的内存都在堆上开辟

4.指令都存放在.text 段上

5.常量字符串,如"hello world"都在.rodata 段存储

 

 

相关标签: 程序