(第17讲)数据结构的堆栈与内存区的堆栈(总结各大神的见解)

来源:互联网 发布:云搜seo 编辑:程序博客网 时间:2024/05/21 12:39

大神1

来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码:

include<stdio.h>

int g1=0,g2=0, g3=0;

intmain() { static int s1=0, s2=0, s3=0; int v1=0, v2=0, v3=0;

//打印出各个变量的内存地址

printf("0x%08x\n",&v1);//打印各本地变量的内存地址printf("0x%08x\n",&v2); printf("0x%08x\n\n",&v3);printf("0x%08x\n",&g1); //打印各全局变量的内存地址 printf("0x%08x\n",&g2);printf("0x%08x\n\n",&g3); printf("0x%08x\n",&s1);//打印各静态变量的内存地址printf("0x%08x\n",&s2); printf("0x%08x\n\n",&s3);return 0; }

编译后的执行结果是:

0x0012ff780x0012ff7c 0x0012ff80

0x004068d00x004068d4 0x004068d8

0x004068dc0x004068e0 0x004068e4

输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量,g1,g2,g3是全局变量,s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的,但是本地变量和全局变量分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。

堆栈是一个先进后出的数据结构,栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程,以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定,这些因素有参数的压入规则和堆栈的平衡。windows API的调用规则和ANSI C的函数调用规则是不一样的,前者由被调函数调整堆栈,后者由调用者调整堆栈。两者通过“stdcall”和“cdecl”前缀区分。先看下面这段代码:

 

大神2

一、预备知识—程序的内存分配

  一个由C/C++编译的程序占用的内存分为以下几个部分

  1、栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

  2、堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

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

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

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

二、例子程序  

  //main.cpp  

  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"优化成一个地方。  

  }  

二、堆和栈的理论知识  

2.1申请方式  

  Stack:由系统自动分配。例如,声明在函数中一个局部变量 int  b;  系统自动在栈中为b开辟空间。

  Heap:需要程序员自己申请,并指明大小,

在c中malloc函数:如p1 = (char*)malloc(10);  

在C++中用new运算符:如p2 = new  char[10];  

但是注意p1、p2本身是在栈中的。  

2.2申请后系统的响应、

栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢。 堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,

  会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表

  中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的

  首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。

  另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部

  分重新放入空闲链表中。  

2.3申请大小的限制  

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

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储

的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小

受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

2.4申请效率的比较:  

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

堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便,另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。

2.5堆和栈中的存储内容  

栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可

执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈

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

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

2.6存取效率的比较  

  char  s1[]   =   "aaaaaaaaaaaaaaa";  

  char  *s2   =   "bbbbbbbbbbbbbbbbb";  

  aaaaaaaaaaa是在运行时刻赋值的;  

  而bbbbbbbbbbb是在编译时就确定的;  

  但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。  

  比如:  

  #include  

  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读取字符,显然慢了。

2.7小结:  

堆和栈的区别可以用如下的比喻来看出:  

使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由

度大。

大神3

二、堆栈缓存方式区别:
1、栈使用的是一级缓存,他们通常都是被调用时处于存储空间中,调用完毕立即释放;
2、堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

三、堆栈数据结构区别:

堆(数据结构):堆可以被看成是一棵树,如:堆排序;

栈(数据结构):一种先进后出的数据结构。

大神4

堆(heap)和栈(stack)是C/C++编程不可避免会碰到的两个基本概念。首先,这两个概念都可以在讲数2据结构的书中找到,他们都是基本的数据结构,虽然栈更为简单一些。

在具体的C/C++编程框架中,这两个概念并不是并行的。对底层机器代码的研究可以揭示,栈是机器系统提供的数据结构,而堆则是C/C++函数库提供的。

具体地说,现代计算机(串行执行机制),都直接在代码底层支持栈的数据结构。这体现在,有专门的寄存器指向栈所在的地址,有专门的机器指令完成数据入栈出栈的操作。

机制的特点是效率高,支持的数据有限,一般是整数,指针,浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构。因为栈的这种特点,对栈的使用在程序中是非常频繁的。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把返回地址推入栈,然后跳转至子程序地址的操作,而子程序中的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。C/C++中的自动变量是直接利用栈的例子,这也就是为什么当函数返回时,该函数的自动变量自动失效的原因。

和栈不同,堆的数据结构并不是由系统(无论是机器系统还是操作系统)支持的,而是由函数库提供的。基本的malloc/realloc/free函数维护了一套内部的堆数据结构。当程序使用这些函数去获得新的内存。

空间时,这套函数首先试图从内部堆中寻找可用的内存空间,如果没有可以使用的内存空间,则试图利用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以适当的形式返回给调用者。当程序释放分配的内存空间时,这片内存空间被返回内部堆结构中,可能会被适当的处理(比如和其他空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。这套复杂的分配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下若干原因:

1.      系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于大量的小内存分类来说会造成浪费。

2.      系统调用申请内存可能是代价昂贵的。系统调用可能涉及用户态和核心态的转换。

3.      没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。

从以上知识可知:

栈是系统提供的功能,特点是快速高效,缺点是有限制,数据不灵活;而堆是函数库提供的功能,特点是灵活方便,数据适应面广泛,但是效率有一定降低。

栈是系统数据结构,对于进程/线程是唯一的;堆是函数库内部数据结构,不一定唯一。不同堆分配的内存无法互相操作。

栈空间分静态分配和动态分配两种。静态分配是编译器完成的,比如自动变量(auto)3的分配。动态分配由malloca函数完成。栈的动态分配无需释放(是自动的),也就没有释放函数。为可移植的程序起见,栈的动态分配操作是不被鼓励的!堆空间的分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存/释放内存匹配是良好程序的基本要素。

大神5

首先在数据结构上要知道堆栈,尽管我们这么称呼它,但实际上堆栈是两种数据结构:堆和栈。堆和栈都是一种数据项按序排列的数据结构。我们先从大家比较熟悉的栈说起吧,它是一种具有后进先出性质的数据结构,也就是说后存放的先取,先存放的后取。这就如同我们要取出放在箱子里面底下的东西(放入的比较早的物体),我们首先要移开压在它上面的物体(放入的比较晚的物体)。而堆就不同了,堆是一种经过排序的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。由于堆的这个特性,常用来实现优先队列,堆的存取是随意,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。

然而我要说的重点并不在这,我要说的堆和栈并不是数据结构的堆和栈,之所以要说数据结构的堆和栈是为了和后面我要说的堆区和栈区区别开来,请大家一定要注意。

下面就说说C语言程序内存分配中的堆和栈,这里有必要把内存分配也提一下,大家不要嫌我啰嗦,一般情况下程序存放在Rom或Flash中,运行时需要拷到内存中执行,内存会分别存储不同的信息,如下图所示:

 

内存中的栈区处于相对较高的地址以地址的增长方向为上的话,栈地址是向下增长的,栈中分配局部变量空间,堆区是向上增长的用于分配程序员申请的内存空间。另外还有静态区是分配静态变量,全局变量空间的;只读区是分配常量和程序代码空间的;以及其他一些分区。


大神6

A:那是不是在内存管理中,凡是采用了栈技术的都成为栈区呢?
B:在咱们的系统中,堆区是采用了堆的数据结构管理,栈区是采用了栈的数据结构管理?
A不对,B正确。
栈区是采用栈技术的内存区域,但不能认定凡是采用栈技术的区域都叫栈区,虽然目前的实际情况是这样。这是一个关于定义的准确问题。

大神7

程序运行时内存的栈和数据结构的栈类似,这个栈主要用来放临时变量和函数参数的,结构也是先进后出.
但内存的堆和数据结构的堆石两码事,内存的堆一般用来动态分配内存的,如malloc(),或c++里的new非配的内存就是动态分配的内存的。至于内存中的堆如何实现的,可能和操作系统和编译器有关,我感觉一般的内存的堆好像是用链表实现的。

0 0
原创粉丝点击