浅析栈区和堆区内存分配的区别

一直以来总是对这个问题的认识比较朦胧,我相信很多朋友也是这样的,总是听到内存一会在栈上分配,一会又在堆上分配,那么它们之间到底是怎么的区别呢?为了说明这个问题,我们先来看一下内存内部的组织情况.

从上图可知,程序占用的内存被分了以下几部分.

1、栈区(stack)
由编译器自动分配释放 ,存放函数的参数值,局部变量的值等,内存的分配是连续的,类似于平时我们所说的栈,如果还不清楚,那么就把它想成数组,它的内存分配是连续分配的,即,所分配的内存是在一块连续的内存区域内.当我们声明变量时,那么编译器会自动接着当前栈区的结尾来分配内存.

2、堆区(heap)
一般由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统回收.类似于链表,在内存中的分布不是连续的,它们是不同区域的内存块通过指针链接起来的.一旦某一节点从链中断开,我们要人为的把所断开的节点从内存中释放.

3、全局区(静态区)(static)
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放

4、文字常量区
常量字符串就是放在这里的。 程序结束后由系统释放

5、程序代码区
存放函数体的二进制代码。

先看一个例子.
char c; //栈上分配
char *p = new char[3]; //堆上分配,将地址赋给了p;

在 编译器遇到第一条指令时,计算其大小,然后去查找当前栈的空间是大于所需分配的空间大小,如果这时栈内空间大于所申请的空间,那么就为其分配内存空间,注 意:在这里,内在空间的分配是连续的,是接着上次分配结束后进行分配的.如果栈内空间小于所申请的空间大小,那么这时系统将揭示栈溢出,并给出相应的异常 信息.

编译器遇到第二条指令时,由于p是在栈上分配的,所以在为p分配内在空间时和上面的方法一样,但当遇到new关 键字,那么编译器都知道,这是用户申请的动态内存空间,所以就会转到堆上去为其寻找空间分配.大家注意:堆上的内存空间不是连续的,它是由相应的链表将其 空间区时的内在区块连接的,所以在接到分配内存空间的指定后,它不会马上为其分配相应的空间,而是先要计算所需空间,然后再到遍列整个堆(即遍列整个链的 节点),将第一次遇到的内存块分配给它.最后再把在堆上分配的字符数组的首地址赋给p.,这个时候,大家已经清楚了,p中现在存放的是在堆中申请的字符数组的首地址,也就是在堆中申请的数组的地址现在被赋给了在栈上申请的指针变量p.为了更加形象的说明问题,请看下图:

从上图可以看出,我们在堆上动态分配的数组的首地址存入了指针p所指向的内容.

请注意:在栈上所申请的内存空间,当我们出了变量所在的作用域后,系统会自动我们回收这些空间,而在堆上申请的空间,当出了相应的作用域以后,我们需要显式 的调用delete来释放所申请的内存空间,如果我们不及时得对这些空间进行释放,那么内存中的内存碎片就越来越多,从而我们的实际内存空间也就会变的越 来越少,即,孤立的内存块越来越多.在这里,我们知道,堆中的内存区域不是连续的,还是将有效的内存区域经过链表指针连接起来的,如果我们申请到了某一块 内存,那么这一块内存区将会从连续的(通过链表连接起来的)内存块上断开,如果我们在使用完后,不及时的对它进行释放,那么它就会孤立的开来,由于没有任 何指针指向它,所以这个区域将成为内存碎片,所以在使用完动态分配的内存(通过NEW申请)后,一定要显式的对它进行DELETE删除.对于这一点,一定 要切记...

上面给大家陈述了它们之间的概念,对于它们俩的使用比较方面,这里我就不能大家断续陈述了,对于这个问题,网上一网友的文章中阐述的比较详细,而且附带了专业的色彩,下面的文章是部分片断.

申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。

堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

堆和栈中的存储内容
栈:
在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

存取效率的比较
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";

aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。

比如:


代码如下:

void main()
{
    char a = 1;
    char c[] = "1234567890";
    char *p ="1234567890";
    a = c[1];
   a = p[1];
    return;
}

对应的汇编代码


代码如下:

10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al

第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。

小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。

使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度

(0)

相关推荐

  • C/C++语言中结构体的内存分配小例子

    当未用 #pragma 指令指定编译器的对齐位数时,结构体按最长宽度的数据成员的宽度对齐:当使用了 #pragma 指令指定编译器的对齐位数时,结构体按最长宽度的数据成员的宽度和 #pragma 指令指定的位数中的较小值对齐. #pragma 指令格式如下所示:#pragma pack(4)     // 或者 #pragma pack(push, 4) 举例如下:(机器字长为 32 位)    struct    {        char a;    }test;    printf("%d

  • JavaScript对内存分配及管理机制详细解析

    你可能听说过JAVA..NET.PHP这些语言有垃圾回收的内存管理机制,但是很少会听到JavaScript也有自己的内存管理机制,JavaScript同样有着类似的垃圾回收功能.本文主要讲述了JavaScript的垃圾回收原理和具体的过程. 简介在底层语言中,比如C,有专门的内存管理机制,比如malloc() 和 free().而Javascript是有垃圾回收(garbage collection)机制的,也就是说JS解释器会自动分配和回收内存.这样就有人觉得,我用的是高级语言,就不用关心内存

  • C#字符串内存分配与驻留池学习分享

    刚开始学习C#的时候,就听说CLR对于String类有一种特别的内存管理机制:有时候,明明声明了两个String类的对象,但是他们偏偏却指向同一个实例.如下: 复制代码 代码如下: String s1 ="Hello";String s2 ="Hello";                       //s2和s1的实际值都是Hellobool same = (object) s1 == (object) s2;//这里比较s1.s2是否引用了同一个对象实例//所

  • PHP变量内存分配问题记录整理

    今天碰到一个关于php变量内存分配的问题,记录一下. 如下这段代码: 复制代码 代码如下: $a = array ( 'str' => 1, 'child' => 2 ); $b = $a; $b['child'] = $a; $b['child']['str'] = 2; echo $b['str']; $b = null; echo $a['str']; 会输出什么呢,结果是11,$b=$a的时候其实并没有新分配内存,ab是指向的同一个区域,$b['child']=$a时,$b会先copy

  • 浅析栈区和堆区内存分配的区别

    一直以来总是对这个问题的认识比较朦胧,我相信很多朋友也是这样的,总是听到内存一会在栈上分配,一会又在堆上分配,那么它们之间到底是怎么的区别呢?为了说明这个问题,我们先来看一下内存内部的组织情况. 从上图可知,程序占用的内存被分了以下几部分. 1.栈区(stack)由编译器自动分配释放 ,存放函数的参数值,局部变量的值等,内存的分配是连续的,类似于平时我们所说的栈,如果还不清楚,那么就把它想成数组,它的内存分配是连续分配的,即,所分配的内存是在一块连续的内存区域内.当我们声明变量时,那么编译器会自

  • C++内存四区之代码区、全局区、栈区和堆区

    C++内存四区 C++ 在程序执行时,将内存大致分为代码区,全局区,栈区和堆区四个区域.不同的区域存储不同的数据,赋予不同的生命周期,能够更灵活地进行编程. 代码区:存放函数体的二进制代码,由操作系统管理创建,代码区时共享的,对于频繁被执行的程序,只需要存有一份代码即可: 全局区:存放全局变量和静态变量以及常量,在程序结束后由操作系统释放: 栈区:由编译其自动分配释放,存放函数的参数值以及局部变量等: 堆区:一般由程序员通过 new 开辟空间,进行分配和释放,若程序员不释放,则程序结束时由操作系

  • C++程序内存栈区与堆区模型案例分析

    目录 栈区: 栈区代码演示: 堆区: 堆区代码演示: new操作符: new操作符代码演示: 栈区: 由编译器自动分配释放,存放函数的参数值,局部变量等(由编译器管理其“生死”) 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放 栈区代码演示: //内存四区-栈区 /* 栈区: 由编译器自动分配释放,存放函数的参数值,局部变量等(由编译器管理其"生死") 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放 */ #include <iostream&

  • C++ 内存分区模型的使用(代码区、全局区、栈区、堆区、new)

    内存分区模型 1 代码区 2 全局区 // 全局变量.静态变量.常量 #include <iostream> using namespace std; // 全局变量.静态变量.常量 //全局变量 int g_a=10; int g_b=10; //const修饰的全局常量 const int c_g_a = 10; const int c_g_b = 10; int main() { //创建普通局部变量 int a = 10; int b = 10; cout << "

  • 详解C++内存的代码区,全局区,栈区和堆区

    目录 代码区: 全局区: 栈区 堆区 总结 今天无意中刷到了一篇关于c++内存的帖子,我发现那个人好像写的不太对,然后同时我自己也发现我对一块还不够了解,所以我干脆就自己去了解整理了一下:首先我们要大概知道四个区都是干什么的 代码区: 顾名思义,就是存放我们写的代码的地方,不过要注意的是存放的是二进制代码. 注意:我们写的所有的写的代码(包括注释.变量.语句等)都会放到代码区中. 全局区: 存放全局,静态变量以及常量. 注意: 1.全局区里有一个部分叫常量区,储存的是常量,如const修饰的全局

  • JVM分配和回收堆外内存的方式与注意点

    目录 JVM内存模型 如何分配堆外内存 第一种方式:ByteBuffer#allocateDirect 第二种方式:Unsafe#allocateMemory 如何回收堆外内存 第一种方式:Unsafe#freeMemory 第二种方式:JVM回收堆外内存 注意点 注意点1: 注意点2: 引用 总结 JVM内存模型 在JVM中内存被分成两大块,分别是堆内存和堆外内存,堆内存就是JVM使用的内存,而堆外内存就是非JVM使用的内存,一般是分配给机器使用的内存. 那么整个内存模型如下: 因此在JVM中

  • Java 内存分配深入理解

    Java 内存分配深入理解 本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java.这类文章网上有很多,但大多比较零碎.本文从认知过程角度出发,将带给读者一个系统的介绍. 进入正题前首先要知道的是Java程序运行在JVM(Java  Virtual Machine,Java虚拟机)上,可以把JVM理解成Java程序和操作系统之间的桥梁,JVM实现了Java的平台无关性,由此可见JVM的重要性.所以在学习Java内存分配原理的时候一定要牢记这一切都是在JVM中进行的,JVM是

  • c语言详解动态内存分配及常见错误的解决

    目录 为什么会有动态内存分配 动态内存函数的介绍 malloc free calloc realloc 常见的错误 对NULL指针的解引用操作 越界访问 对非动态内存进行free 使用free释放动态开辟内存的一部分 对同一块动态内存多次释放 对动态内存内存忘记释放(内存泄漏) 为什么会有动态内存分配 内存使用方式有两种 1.创建一个变量 2.创建一个数组 int a = 1; int arr[10]; 但是这两种方式都有一些特点 1.空间是固定大小的,不会变 2.必须提前知道要开辟多少空间,必

  • golang数组内存分配原理

    目录 编译时数组类型解析 ArrayType types2.Array types.Array 编译时数组字面量初始化 编译时数组索引越界检查 运行时数组内存分配 总结 编译时数组类型解析 ArrayType 数组是内存中一片连续的区域,在声明时需要指定长度,数组的声明有如下三种方式,[...]的方式在编译时会自动推断长度. var arr1 [3]int var arr2 = [3]int{1,2,3} arr3 := [...]int{1,2,3} 在词法及语法解析时,上述三种方式声明的数组

  • C/C++堆区专篇精讲

    目录 malloc free new delete memcpy memset malloc malloc开辟堆区内存.头文件stdlib.h,函数原形如下. void*malloc(size_tsize); //返回值void指针,该指针就是开辟的内存地址,参数是开辟的字节数. free free释放堆区开辟的内存.头文件stdlib.h,函数原形如下. voidfree(void*ptr); // 参数传入需要释放的堆区内存首地址. 程序: #include<iostream> #incl

随机推荐