精华内容
下载资源
问答
  • 堆与栈的区别

    万次阅读 多人点赞 2018-06-29 15:24:05
    堆(Heap)与栈(Stack)是开发人员必须面对的两个概念,在理解这两个概念时,需要放到具体的场景下,因为不同场景下,堆与栈代表不同的含义。一般情况下,有两层含义: (1)程序内存布局场景下,堆与栈表示的是...

    堆(Heap)与栈(Stack)是开发人员必须面对的两个概念,在理解这两个概念时,需要放到具体的场景下,因为不同场景下,堆与栈代表不同的含义。一般情况下,有两层含义:
    (1)程序内存布局场景下,堆与栈表示两种内存管理方式;
    (2)数据结构场景下,堆与栈表示两种常用的数据结构。

    1.程序内存分区中的堆与栈

    1.1 栈简介

    栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈。参考如下代码:

    int main() {
    	int b;				//栈
    	char s[] = "abc"; 	//栈
    	char *p2;			//栈
    }
    

    其中函数中定义的局部变量按照先后定义的顺序依次压入栈中,也就是说相邻变量的地址之间不会存在其它变量。栈的内存地址生长方向与堆相反,由高到底,所以后定义的变量地址低于先定义的变量,比如上面代码中变量 s 的地址小于变量 b 的地址,p2 地址小于 s 的地址。栈中存储的数据的生命周期随着函数的执行完成而结束。

    1.2 堆简介

    堆由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收,分配方式类似于链表。参考如下代码:

    int main() {
    	// C 中用 malloc() 函数申请
    	char* p1 = (char *)malloc(10);
    	cout<<(int*)p1<<endl;		//输出:00000000003BA0C0
    	
    	// 用 free() 函数释放
    	free(p1);
       
    	// C++ 中用 new 运算符申请
    	char* p2 = new char[10];
    	cout << (int*)p2 << endl;		//输出:00000000003BA0C0
    	
    	// 用 delete 运算符释放
    	delete[] p2;
    }
    

    其中 p1 所指的 10 字节的内存空间与 p2 所指的 10 字节内存空间都是存在于堆。堆的内存地址生长方向与栈相反,由低到高,但需要注意的是,后申请的内存空间并不一定在先申请的内存空间的后面,即 p2 指向的地址并不一定大于 p1 所指向的内存地址,原因是先申请的内存空间一旦被释放,后申请的内存空间则会利用先前被释放的内存,从而导致先后分配的内存空间在地址上不存在先后关系。堆中存储的数据若未释放,则其生命周期等同于程序的生命周期。

    关于堆上内存空间的分配过程,首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确地释放本内存空间。由于找到的堆节点的大小不一定正好等于申请的大小,系统会自动地将多余的那部分重新放入空闲链表。

    1.3 堆与栈区别

    堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:
    (1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;

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

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

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

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

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

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

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

    2.数据结构中的堆与栈

    数据结构中,堆与栈是两个常见的数据结构,理解二者的定义、用法与区别,能够利用堆与栈解决很多实际问题。

    2.1 栈简介

    栈是一种运算受限的线性表,其限制是指只仅允许在表的一端进行插入和删除操作,这一端被称为栈顶(Top),相对地,把另一端称为栈底(Bottom)。把新元素放到栈顶元素的上面,使之成为新的栈顶元素称作进栈、入栈或压栈(Push);把栈顶元素删除,使其相邻的元素成为新的栈顶元素称作出栈或退栈(Pop)。这种受限的运算使栈拥有“先进后出”的特性(First In Last Out),简称FILO。

    栈分顺序栈和链式栈两种。栈是一种线性结构,所以可以使用数组或链表(单向链表、双向链表或循环链表)作为底层数据结构。使用数组实现的栈叫做顺序栈,使用链表实现的栈叫做链式栈,二者的区别是顺序栈中的元素地址连续,链式栈中的元素地址不连续。

    栈的结构如下图所示:
    这里写图片描述
    栈的基本操作包括初始化、判断栈是否为空、入栈、出栈以及获取栈顶元素等。下面以顺序栈为例,使用 C++ 给出一个简单的实现。

    #include<stdio.h>
    #include<malloc.h>
    
    #define DataType int
    #define MAXSIZE 1024
    struct SeqStack {
    	DataType data[MAXSIZE];
    	int top;
    };
    
    //栈初始化,成功返回栈对象指针,失败返回空指针NULL
    SeqStack* initSeqStack() {
    	SeqStack* s=(SeqStack*)malloc(sizeof(SeqStack));
    	if(!s) {
    		printf("空间不足\n");
    		return NULL;
    	} else {
    		s->top = -1;
    		return s;
    	}
    }
    
    //判断栈是否为空
    bool isEmptySeqStack(SeqStack* s) {
    	if (s->top == -1)
    		return true;
    	else
    		return false;
    }
    
    //入栈,返回-1失败,0成功
    int pushSeqStack(SeqStack* s, DataType x) {
    	if(s->top == MAXSIZE-1)
    	{
    		return -1;//栈满不能入栈
    	} else {
    		s->top++;
    		s->data[s->top] = x;
    		return 0;
    	}
    }
    
    //出栈,返回-1失败,0成功
    int popSeqStack(SeqStack* s, DataType* x) {
    	if(isEmptySeqStack(s)) {
    		return -1;//栈空不能出栈
    	} else {
    		*x = s->data[s->top];
    		s->top--;
    		return 0;
    	}
    }
    
    //取栈顶元素,返回-1失败,0成功
    int topSeqStack(SeqStack* s,DataType* x) {
    	if (isEmptySeqStack(s))
    		return -1;	//栈空
    	else {
    		*x=s->data[s->top];
    		return 0;
    	}
    }
    
    //打印栈中元素
    int printSeqStack(SeqStack* s) {
    	int i;
    	printf("当前栈中的元素:\n");
    	for (i = s->top; i >= 0; i--)
    		printf("%4d",s->data[i]);
    	printf("\n");
    	return 0;
    }
    
    //test
    int main() {
    	SeqStack* seqStack=initSeqStack();
    	if(seqStack) {
    		//将4、5、7分别入栈
    		pushSeqStack(seqStack,4);
    		pushSeqStack(seqStack,5);
    		pushSeqStack(seqStack,7);
    		
    		//打印栈内所有元素
    		printSeqStack(seqStack);
    		
    		//获取栈顶元素
    		DataType x=0;
    		int ret=topSeqStack(seqStack,&x);
    		if(0==ret) {
    			printf("top element is %d\n",x);
    		}
    		
    		//将栈顶元素出栈
    		ret=popSeqStack(seqStack,&x);
    		if(0==ret) {
    			printf("pop top element is %d\n",x);
    		}
    	}
    	return 0;
    }
    

    运行上面的程序,输出结果:

    当前栈中的元素:
       7   5   4
    top element is 7
    pop top element is 7
    

    2.2 堆简介

    2.2.1 堆的性质

    堆是一种常用的树形结构,是一种特殊的完全二叉树,当且仅当满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆。堆的这一特性称之为堆序性。因此,在一个堆中,根节点是最大(或最小)节点。如果根节点最小,称之为小顶堆(或小根堆),如果根节点最大,称之为大顶堆(或大根堆)。堆的左右孩子没有大小的顺序。下面是一个小顶堆示例:
    这里写图片描述
    堆的存储一般都用数组来存储堆,i节点的父节点下标就为(i1)/2(i – 1) / 2。它的左右子节点下标分别为 2i+12 * i + 12i+22 * i + 2。如第0个节点左右子节点下标分别为1和2。
    这里写图片描述

    2.2.2 堆的基本操作

    (1)建立
    以最小堆为例,如果以数组存储元素时,一个数组具有对应的树表示形式,但树并不满足堆的条件,需要重新排列元素,可以建立“堆化”的树。
    这里写图片描述
    (2)插入
    将一个新元素插入到表尾,即数组末尾时,如果新构成的二叉树不满足堆的性质,需要重新排列元素,下图演示了插入15时,堆的调整。
    这里写图片描述
    (3)删除。
    堆排序中,删除一个元素总是发生在堆顶,因为堆顶的元素是最小的(小顶堆中)。表中最后一个元素用来填补空缺位置,结果树被更新以满足堆条件。
    这里写图片描述

    2.2.3 堆操作实现

    (1)插入代码实现
    每次插入都是将新数据放在数组最后。可以发现从这个新数据的父节点到根节点必然为一个有序的数列,现在的任务是将这个新数据插入到这个有序数据中,这就类似于直接插入排序中将一个数据并入到有序区间中,这是节点“上浮”调整。不难写出插入一个新数据时堆的调整代码:

    //新加入i节点,其父节点为(i-1)/2
    //参数:a:数组,i:新插入元素在数组中的下标  
    void minHeapFixUp(int a[], int i) {  
        int j, temp;  
        temp = a[i];  
        j = (i-1)/2;      //父节点  
        while (j >= 0 && i != 0) {  
            if (a[j] <= temp)//如果父节点不大于新插入的元素,停止寻找  
                break;  
            a[i]=a[j];    		//把较大的子节点往下移动,替换它的子节点  
            i = j;  
            j = (i-1)/2;  
        }  
        a[i] = temp;  
    }  
    

    因此,插入数据到最小堆时:

    //在最小堆中加入新的数据data  
    //a:数组,index:插入的下标,
    void minHeapAddNumber(int a[], int index, int data) {  
        a[index] = data;  
        minHeapFixUp(a, index);  
    } 
    

    (2)删除代码实现
    按照堆删除的说明,堆中每次都只能删除第0个数据。为了便于重建堆,实际的操作是将数组最后一个数据与根节点交换,然后再从根节点开始进行一次从上向下的调整。

    调整时先在左右儿子节点中找最小的,如果父节点不大于这个最小的子节点说明不需要调整了,反之将最小的子节点换到父节点的位置。此时父节点实际上并不需要换到最小子节点的位置,因为这不是父节点的最终位置。但逻辑上父节点替换了最小的子节点,然后再考虑父节点对后面的节点的影响。堆元素的删除导致的堆调整,其整个过程就是将根节点进行“下沉”处理。下面给出代码:

    //a为数组,len为节点总数;从index节点开始调整,index从0开始计算index其子节点为 2*index+1, 2*index+2;len/2-1为最后一个非叶子节点 
    void minHeapFixDown(int a[],int len,int index) {
    	if(index>(len/2-1))//index为叶子节点不用调整
    		return;
    	int tmp=a[index];
    	lastIndex=index;
    	while(index<=len/2-1)        //当下沉到叶子节点时,就不用调整了
    	{ 
    		// 如果左子节点小于待调整节点
    		if(a[2*index+1]<tmp) {
    			lastIndex = 2*index+1;
    		}
    		//如果存在右子节点且小于左子节点和待调整节点
    		if(2*index+2<len && a[2*index+2]<a[2*index+1]&& a[2*index+2]<tmp) {
    			lastIndex=2*index+2;
    		}
    		//如果左右子节点有一个小于待调整节点,选择最小子节点进行上浮
    		if(lastIndex!=index) {
    			a[index]=a[lastIndex];
    			index=lastIndex;
    		} else break;             //否则待调整节点不用下沉调整
    	}
    	a[lastIndex]=tmp;           //将待调整节点放到最后的位置
    }
    

    根据堆删除的下沉思想,可以有不同版本的代码实现,以上是和孙凛同学一起讨论出的一个版本,在这里感谢他的参与,读者可另行给出。个人体会,这里建议大家根据对堆调整过程的理解,写出自己的代码,切勿看示例代码去理解算法,而是理解算法思想写出代码,否则很快就会忘记。

    (3)建堆
    有了堆的插入和删除后,再考虑下如何对一个数据进行堆化操作。要一个一个的从数组中取出数据来建立堆吧,不用!先看一个数组,如下图:
    这里写图片描述
    很明显,对叶子节点来说,可以认为它已经是一个合法的堆了即20,60, 65, 4, 49都分别是一个合法的堆。只要从A[4]=50开始向下调整就可以了。然后再取A[3]=30,A[2] = 17,A[1] = 12,A[0] = 9分别作一次向下调整操作就可以了。下图展示了这些步骤:
    这里写图片描述
    写出堆化数组的代码:

    //建立最小堆
    //a:数组,n:数组长度
    void makeMinHeap(int a[], int n) {  
        for (int i = n/2-1; i >= 0; i--)  
            minHeapFixDown(a, i, n);  
    }  
    

    2.2.4 堆的具体应用——堆排序

    堆排序(Heapsort)是堆的一个经典应用,有了上面对堆的了解,不难实现堆排序。由于堆也是用数组来存储的,故对数组进行堆化后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。有点类似于直接选择排序。

    因此,完成堆排序并没有用到前面说明的插入操作,只用到了建堆和节点向下调整的操作,堆排序的操作如下:

    //array:待排序数组,len:数组长度
    void heapSort(int array[],int len) {
    	//建堆
    	makeMinHeap(array,len); 
    	
    	//最后一个叶子节点和根节点交换,并进行堆调整,交换次数为len-1次
    	for(int i=len-1;i>0;--i) {
    		//最后一个叶子节点交换
    		array[i]=array[i]+array[0];
    		array[0]=array[i]-array[0];
    		array[i]=array[i]-array[0];
            
            //堆调整
    		minHeapFixDown(array, 0, len-i-1);  
    	}
    }  
    

    (1)稳定性。堆排序是不稳定排序。

    (2)堆排序性能分析。由于每次重新恢复堆的时间复杂度为O(logN),共N-1次堆调整操作,再加上前面建立堆时N/2次向下调整,每次调整时间复杂度也为O(logN)。两次操作时间复杂度相加还是O(NlogN),故堆排序的时间复杂度为O(NlogN)。

    最坏情况:如果待排序数组是有序的,仍然需要O(NlogN)复杂度的比较操作,只是少了移动的操作;

    最好情况:如果待排序数组是逆序的,不仅需要O(NlogN)复杂度的比较操作,而且需要O(NlogN)复杂度的交换操作,总的时间复杂度还是O(NlogN)。

    因此,堆排序和快速排序在效率上是差不多的,但是堆排序一般优于快速排序的重要一点是数据的初始分布情况对堆排序的效率没有大的影响。


    参考文献

    [1] 浅谈堆和栈的区别
    [2] 栈内存和堆内存的区别
    [3] 浅谈内存分配方式以及堆和栈的区别(很清楚)
    [4] C++函数调用过程深入分析
    [5] 十种排序算法

    杂注

    我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2z2o0f9ecoo44

    展开全文
  • 堆与栈

    2019-09-21 18:18:45
    数据结构中的堆与栈 堆 栈 内存分配中的堆与栈 数据结构中的堆与栈 堆 通常是一个可以被看做一棵树的数组对象。 堆总是满足下列性质: a. 堆中某个节点的值总是不大于或不小于其父节点的值; b. 堆总是...

    目录

     

    数据结构中的堆与栈

    内存分配中的堆与栈

     


    数据结构中的堆与栈

    通常是一个可以被看做一棵树的数组对象。

    堆总是满足下列性质:

            a. 堆中某个节点的值总是不大于或不小于其父节点的值;

            b. 堆总是一棵完全二叉树。

    将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

    又称堆栈,是允许在同一端进行插入和删除操作的特殊线性表。插入一般称为进栈(PUSH),删除则称为退栈(POP)。

    a. 允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);

    b. 栈底固定,而栈顶浮动;

    c. 栈中元素个数为零时称为空栈。

    d. 栈也称为先进后出表。


    内存分配中的堆与栈

     

     
    存放内容 动态申请数据 局部变量
    空间分配/释放方式 手动分配/释放 操作系统自动分配/释放
    空间 很大的自由存储区 空间有限
    申请效率

     

    除此之外,栈处于相对较高的地址,其栈地址是向下增长的,而堆地址是向上增长的。

     

    展开全文
  • 2013-05-25 01:47:48
    一、预备知识―程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1、区(stack)― 由编译器自动分配释放...注意它数据结构中的是两回事,分配方式倒是类似于链表,呵呵。 3、全局区(静态
    一、预备知识―程序的内存分配 
    
    一个由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 = (char *)malloc(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小结: 
    
    堆和栈的区别可以用如下的比喻来看出: 
    
    使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。 
    
    使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
    展开全文
  • 理解堆与栈 导航 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 栈基本工作原理 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack...

    理解堆与栈

    导航

     

    深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈

    深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 栈基本工作原理

    深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第三节 栈与堆,值类型与引用类型

    深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第四节 参数传递对堆栈的影响 1

    深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第四节 参数传递对堆栈的影响 2

    深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复

    深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第六节 理解垃圾回收GC,提搞程序性能

     

     

    前言

     

    虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC)。另外,了解内存管理可以帮助我们理解在每一个程序中定义的每一个变量是怎样工作的。

     

     

    简介

    这篇文章会包含堆与栈的基础知识,变量类型,变量工作原理。

    在程序运行时,.NET FRAMEWORK把对象存储在内存中的两个位置:堆与栈,并且它们都会帮助我们更好的运行程序。堆与栈寄存在电脑的操作内存中,并包含我们需要的信息使整个程序运行正常。

     

    堆与栈:有什么不同?

     

    ,或多或少负责跟踪正在程序中运行的代码。

    ,或多或少负责跟踪程序对象或数据。

     

    栈,把它想像成叠在一起的盒子(像搭积木一样)。每一次调用一个方法就会在最上面叠一个盒子,用来跟踪程序运行情况。我们只能使用栈中叠在最上面的盒子里的东西。当某一最上面的盒子里的代码执行完毕(如方法执行完成),就把它扔掉并继续去使用下一个盒子。

    堆,与栈类似,只是它是用来保存信息而不是跟踪执行。所以,堆里的任何信息都可以在任何时间被访问。有了堆,访问信息没有约束,而不像栈只能访问最上面的盒子。

    堆的情况就像你把一堆刚洗完的衣服放在床上还没有时间来的及收走,你可以迅速拿到你想要拿的衣服。栈的情况就像你叠在一起的鞋盒子,你需要拿走最上面的盒子才能拿到下一个盒子。

     

     

    上图并不上真正的内存运行情况,只是为了让大家区分堆和栈。

    栈,会自我管理,它有自己的内存管理机制。当最上面的盒子不再使用时,会自动被扔掉。

    堆,相反,我们要控制它的垃圾回收(GC)。我们要去管理堆是否干净,就像管理床上的脏衣服。你不手动扔掉它,就会在床上变臭。

     

    什么在堆和栈里

     

    当程序执行时,我们主要有4种类型的东西放进堆和栈里:值类型,引用类型,指针,指令。

     

    值类型:

    • bool
    • byte
    • char
    • decimal
    • double
    • enum
    • float
    • int
    • long
    • sbyte
    • short
    • struct
    • uint
    • ulong
    • ushort

    它们都衍生于System.ValueType。

     

    引用类型:

    • class
    • interface
    • delegate
    • object
    • string

    它们都衍生于System.Object。当然object就是System.Object。

     

     

    指针:

     

    第三种被放于内存管理体制中的是类型的引用。这个引用通常被叫作指针。我们并不具体的使用指针,它们由CLR管理。一个指针(引用)是不同于引用类型的。我们定义它是一个引用类型,意味着我们可以通过指针访问它。一个指针占有一小块内存,这块内存指向另一块内存。指针占用在内存中的存储和其它的相同,只是存放的值既不是内存地址也不是空null。

     

     

     

    指令:

     

    我们会在后面的文章中介绍指令怎么工作。

     

     

     

    总结

     

    第一节到此结束,以后的章节里会介绍不同对象在堆和栈里存放的不同。

     

    翻译于: http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx

    展开全文
  • Q61:堆与栈

    2019-07-03 14:32:28
    堆与栈相关。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 19,128
精华内容 7,651
关键字:

堆与栈