[程序设计语言] 堆和栈的全面总结

操作系统堆栈:

分配由编译器自己主动和自己主动释放。对应于堆栈的函数。参数存储功能值、函数调用结束后完成值和局部变量的函数体内。段内存空间。其操作和组织方式与数据结构中的栈十分相似。栈是为了运行线程留出的内存空间。当调用函数时创建栈。当函数运行完毕,栈就被回收了。

操作系统中的堆:

由程序猿手动进行内存的申请与释放。因为程序猿手动申请及释放的内存块存放在堆中。堆中有非常多内存块,所以堆的组织方式类似于链表。操作系统中的堆与数据结构中的堆全然不同。我认为通俗的理解能够是这种:数据结构中的堆是"结构堆"。有严谨的逻辑和操作方式。而操作系统中的堆。更像是使用链表将"一堆杂乱的东西"联系起来。堆是为动态分配预留的内存空间,其生命周期为整个应用程序的生命周期。

当应用程序结束以后,堆開始被回收。

每个线程都有一个属于自己的栈,但每个应用程序通常仅仅有一个堆(一个应用程序使用了多个堆的情况也是有的)。当线程被创建的时候。设置了栈的大小。

在应用程序启动的时候,设置了堆的大小。栈的大小一般是固定的,可是堆能够在须要的时候进行扩展,如程序猿向操作系统申请很多其它内存的时候。

因为栈的工作方式类似于数据结构中的栈,堆的工作方式类似于链表,所以栈显然会比堆快得多。依照栈的存取方式,想要释放内存或是新增内存。仅仅须要对应移动栈顶指针就可以。堆则要首先在内存的空暇区域寻找合适的内存空间,然后占用。然后指向这块空间。显然堆比栈要复杂得多。

接下来本来是想将栈和堆分开进行陈述,斟酌了一下还是决定从同一方面对栈和堆进行比較。有了比較才明显。

1. 在创建栈的时候栈的大小就固定了,由于栈要连续占用一段空间。依据上文所属的堆的特性,决定了堆的大小是动态的,其分配和释放也是动态的。

2. 栈中的数据过多会导致爆栈。比方dfs写搓了。

而假如堆也爆了的话。。

。那说明内存也爆了。

3. 每一个函数的栈都是各自独立的,可是一个应用程序的堆是被全部的栈共享。

既然提到共享,那么这里就有"并行存取"的问题了。实际上并行存取是由堆控制的。而不是被栈控制的。

4. 栈的作用域仅限于函数内部,栈在函数结束的时候会自行释放掉空间。可是创建于堆上的变量必需要手动释放。堆中的变量不存在作用域的问题。由于堆是全局的。

5. 栈中存放的是函数返回值地址、函数參数。函数内的局部变量等。堆中存放的是由程序猿手动进行申请的内存块(malloc、new等)。

6. 堆和栈都按需进行分配。栈有严格的容量上限。而堆的容量上限则是"不严格"的。堆并没有固定的容量上限,它与当前的剩余内存量有关(事实上还不准确,操作系统还有虚拟内存或其它概念,所以堆的工作方式较为抽象)。

7. 通过移动栈顶指针就可以实现栈内存的分配。

在堆上分配内存的做法则是从当前空暇的内存中找一块满足大小的区域。就像链表的工作方式一样。

8. 仅仅要没有超出栈容量。栈能够进行随意的释放和申请内存。并不会造成内存出现故障,是安全的。而堆不同,大量申请和释放小内存块可能会造成内存问题,这些小的内存块零散的分布在内存中。导致兴许大块的内存申请失败。由于尽管空暇的内存足够多。可是并不连续。这样的情况下的小块内存叫做"堆碎片"。只是这并非什么大问题。详细详见"操作系统"的有关知识。

9. 栈在确定了栈底地址后,其栈顶指针从栈底地址開始。逐渐向低地址走。也就是说栈的存储空间是从高地址走向低地址的。

堆则相反,堆在申请空间的时候通常逐渐往高地址的方向来寻找可用内存。

纯粹的文字描写叙述显得枯燥无味,我们来看一些代码:

#include <iostream>
using namespace std;

void func()
{
   int i = 5;
   int j = 3;
   int k = 7;
   int *p = &i;
   printf("%d\n", *p);
   printf("%d\n", *(p-1));
   printf("%d\n", *(p-2));

}

int main()
{
   func();

   getchar();
   return 0;
}

上述代码的结果是:5 3 7

从结果中我们能够看出两件事:

一是栈地址是连续的,我们能够通过一个指针和一个相对的大小,来"偏移"到别的变量上去。

二是从中能够看出栈地址是从高到低分布的,栈底在高地址。朝低地址的方向生长。

所以程序中是p-1而不是p+1。

void func()
{
	int *p = NULL;
	// 上行代码是个重点。这个指针待会会用于申请新的内存。
	// 此时除了它自身作为一个变量须要占用4字节的空间(指针都占4字节),没有不论什么其它空间被申请。
	// 这个指针变量是函数的局部变量,所以它被创建在栈上。
	int num = 100; 		// 这个变量相同创建于栈上。
	int buffer[100];	// 相同的,buffer占用了栈的400字节的空间
	p = new int[100];	// 注意,程序猿手动申请了一块空间,这400字节的内存创建于堆上。
	// 所以此刻p的状态是:p为函数局部变量,它指向了一块全局范围的内存空间。
}
// 函数体结束。上述函数有个严重的问题,那就是指针p的内存泄露。
// 正确的做法是在函数最后delete掉这块内存。或是返回这块内存的地址以供继续使用。

接下来我们来了解一下当调用一个函数的时候所发生的事情:

首先操作系统为这个函数分配了一个栈。由于在调用完这个函数以后须要能正确返回到下一条语句并继续运行,所以第一步是将调用完函数的下一条指令的地址压入栈。这样当函数调用完毕,栈顶指针一点点释放内存以后。栈顶指针指向了这个地址,就能返回到正确的位置继续运行了。

int main()
{
	func();
	printf("%d\n", 100);
	return 0;
}

比方上述代码,在调用func之前,首先把func的下一条语句,也就是printf语句的地址。存在栈中。

这样函数调用完毕后就能正确返回到这个printf并继续往后运行了。

注意这里的地址是指令地址,而不是变量地址什么的。它有那么点类似于操作系统中的程序计数器(PC,即Program Counter)。

然后把实參从右到左的顺序依次入栈(大多数的C/C++编译器为从右到左)接着是函数中的各种局部变量。要注意的是函数中的static变量是不入栈的。

全局变量和static变量在编译的时候就已经在静态存储区分配好内存了。

假设这个时候该函数又调用了其他函数,过程也是一样的。首先是返回地址,然后是參数和局部变量。

这样在每层调用结束,栈顶指针不断下降(释放内存)的时候,就能正确返回到之前调用的位置并继续往下运行了。

出栈。或者说释放内存的过程。依据栈的特性,是相反的,所以就不赘述了。

一个 C或C++程序,它眼中的内存地址分分为这么五个区域:

栈区(stack)、堆区(heap)、全局静态区(static)、文字常量区和程序指令区。

栈区和堆区前面已经介绍过。全局静态区用于存放全局变量和静态static静态变量。全局静态区分为两块内容:一块用于初始化以后的全局变量和静态变量,一块用于未初始化的全局变量和静态变量。全局静态区和堆一样,程序结束后由操作系统进行释放。文字常量区用于存放常量字符串。程序结束后由操作系统进行释放。

程序指令区最好理解,就是存放程序代码的二进制指令。

int cnt;		// 存放在全局静态区的未初始化区
int num = 0;	// 存放在全局静态区的已初始化区
int *p;			// 存放在全局静态区的未初始化区
int main()
{
	int i, j, k;			// 存放在栈区
	int *pBuffer = (int *)malloc(sizeof(int) * 10);	// 指针pBuffer在栈中,该内存在堆中
	char *s = "hactrox";	// 指针s存放在栈中,字符串存放在文字常量区中
	char str[] = "hactrox";	// str和字符串存放在栈中
	static int a = 0;		// a存放在全局静态区的已初始化区
}

char *s = "hactrox";    // "hactrox"在文字常量区。s指向这个区域中的"hactrox",所以这能够理解为。首先在文字常量区创建了这个字符串,然后s指向这个字符串这样两个步骤。

s本身作为一个局部变量存储在栈中。

// 以下的代码是错误的,指针还没指向就直接赋值了?

int *p = 5;

// 以下的代码才是正确的,首先要创建这个int型变量,然后p指向这个变量。new来的int变量在堆中。

int *p = new int(5);

接下来我们看一看一个很常见的问题:下述代码有没有什么问题?有问题的话问题在哪里?

#include <iostream>
using namespace std;

char* f1()
{
   char *s = "hactrox";
   return s;
}

char* f2()
{
   char s[] = "hactrox";
   return s;
}

int main()
{
   printf("%s\n", f1());
   printf("%s\n", f2());

   getchar();
   return 0;
}

问题在于第二个函数,f2并不能正确返回那个字符串。在函数f1中。"hactrox"字符串创建于文字常量区。然后返回该常量字符串的地址,由于文字常量区的字符串是全局的,尽管指针s是局部变量,可是s在消亡前已经把目标地址送出来了。所以s消亡与否不是重点。重点是返回的地址所指向的区域还在,所以能正确显示。在函数f2中。“hactrox”与s均为局部变量。它们保存在栈中。

尽管s相同返回了一个地址。但这个地址所指向的内存已经被释放掉了。地址有效,但目标已无效。所以输出的仅仅是乱码。

关于文字常量区另一些东西要说(好像和本博客的主题扯远了?)

#include <iostream>
using namespace std;

void func()
{
	char *str1 = "123";
	printf("%x\n", str1);
	char *str2 = "123";
	// 同在文字常量区。编译器可能会将str2直接指向str1所指向的内存。
	// 而不是开辟新的空间来存放第二个同样字符串。
	// 通过打印str2的指针可验证
	printf("%x\n", str2);

	char *s1 = "hactrox";
	printf("%x\n", s1);
	char *s2 = "hactrox";
	printf("%x\n", s2);
}
int main()
{
	func();

	getchar();
	return 0;
}

char s[] = "hactrox";

char *s = "hactrox again";

第二段代码,即文字常量区变量在编译的时候就已经确定了,

而第一段代码。是在执行的时候进行赋值的。

这样看起来貌似第二段代码的效率要高。事实上不然,

当在执行时刻用到这两个变量的时候,对于第一段代码。直接读取字符串,而对于第二段代码,首先读取该字符串指针。然后依据指针再读取字符串,显然效率就下降了。

事实上我认为关注栈和堆,事实上主要是关注作用域、生命周期和有效性的问题。

指针被释放了。不代表指针指向的内存会被释放。相同的。指针指向的内存被释放了,不代表指针会被同步释放或自己主动指向NULL,指针依然指向那块已经失效了的地址。这个地址不能用于。没有人能保证较长的有效地址,接下来会发生什么。

版权声明:本文博客原创文章,博客,未经同意,不得转载。

时间: 2024-11-07 14:47:36

[程序设计语言] 堆和栈的全面总结的相关文章

C语言堆和栈的区别

一.预备知识—程序的内存分配    一个由C/C++编译的程序占用的内存分为以下几个部分    1.栈区(stack)—   由编译器自动分配释放   ,存放函数的参数值,局部变量的值等.其    操作方式类似于数据结构中的栈.    2.堆区(heap)   —   一般由程序员分配释放,   若程序员不释放,程序结束时可能由OS回    收   .注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵.    3.全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的

C语言中的堆与栈20160604

首先声明这里说的是C语言中的堆与栈,并不是数据结构中的!一.前言介绍:C语言程序经过编译连接后形成编译.连接后形成的二进制映像文件由栈,堆,数据段(由三部分部分组成:只读数据 段,已经初始化读写数据段,未初始化数据段即BBS)和代码段组成,如下图所示: 其中所谓静态,就是一定会存在的而且会永恒存在.不会消失,这样的数据包括常量.常变量(const 变量).静态变 量.全局变量等. 动态的话,就是会变化的了.动态的区域,就是堆和栈.这个栈应该称作 call stack,上面会存放函数的返回地址.

C语言中堆和栈的区别

原文:http://blog.csdn.net/tigerjibo/article/details/7423728 一.前言: C语言程序经过编译连接后形成编译.连接后形成的二进制映像文件由栈,堆,数据段(由三部分部分组成:只读数据段,已经初始化读写数据段,未初始化数据段即BBS)和代码段组成,如下图所示: 1.栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量等值.其操作方式类似于数据结构中的栈. 2.堆区(heap):一般由程序员分配释放,若程序员不释放,则可能会引起内存泄

C语言堆栈入门——堆和栈的区别 -- 转

在 计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念.堆栈:一种数据结构.一 个在程序运行时用于存放的地方,这可能是很多初学者的认识,因为我曾经就是这么想的和汇编语言中的堆栈一词混为一谈.我身边的一些编程的朋友以及在网上看 帖遇到的朋友中有好多也说不清堆栈,所以我想有必要给大家分享一下我对堆栈的看法,有说的不对的地方请朋友们不吝赐教,这对于大家学习会有很大帮助. 数据结构的栈和堆 首先在数据结构上要知道堆栈,尽管我们这么称呼

C语言中堆、栈、队列

C语言中堆.栈和队列: 1.堆和栈 (1)数据结构的堆和栈 堆栈是两种数据结构. 栈(栈像装数据的桶或箱子):是一种具有后进先出性质的数据结构,也就是说后存放的先取,先存放的后取.这就如同要取出放在箱子里面底下的东西(放入的比较早的物体),首先要移开压在它上面的物体(放入的比较晚的物体). 堆(堆像一棵倒过来的树):是一种经过排序的树形数据结构,每个结点都有一个值.通常所说的堆的数据结构,是指二叉堆.堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆.由于堆的这个特性,常用来实现优

Jvm(29),理解升级----C语言中的堆和栈的区别 (可以借鉴)

在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念. 堆栈:一种数据结构.一个在程序运行时用于存放的地方,这可能是很多初学者的认识,因为我曾经就是这么想的和汇编语言中的堆栈一词混为一谈.我身边的一些编程的朋友以及在网上看帖遇到的朋友中有好多也说不清堆栈,所以我想有必要给大家分享一下我对堆栈的看法,有说的不对的地方请朋友们不吝赐教,这对于大家学习会有很大帮助. 一.数据结构中的栈和堆 首先在数据结构上要知道堆栈,尽管我们这么称

[java学习笔记]java语言基础概述之内存的划分&amp;堆和栈

1.内存的划分 1.寄存器         cpu处理 2.本地方法区        和所在系统相关 3.方法区 4.栈内存 5.堆内存 2.栈和堆 1.栈:      存储的都是局部变量.而且变量所属的作用域一旦结束,就释放该变量.      栈中的变量生命周期都很短,更新速度会很快. 局部代码块:限定局部变量的生命周期.局部代码块一旦执行结束,里面的变量就会被释放. 2.堆      存储的是数组和对象.凡是new建立的,都会存在堆里.      特点: 每一个实体都有首地址值. 堆内存中的

C语言堆栈入门——堆和栈的区别

来看一个网上很流行的经典例子: 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); 堆 }

深入浅出C语言中的堆和栈

在谈堆栈的时候,我在这有必要把计算机的内存结构给大家简单的介绍下(高手们可以直接飘过) 一. 内存结构 每个程序一启动都有一个大小为4GB的内存,这个内存叫虚拟内存,是概念上的,真正能用到的,只是很小一部分,一般也就是在几百K到几百M.我们PC中内存,我们称之为物理内存,也就是256M,512M等,虚拟内存和物理内存的如何转换是由操作系统完成的,我们不需要管它.我们只需要管好我们自己程序的那4GB内存就可以了. 要管理4GB的虚拟内存,就必须给每个字节分配一个号码,以便程序与访问到其中任何一个字