堆 栈 详 解

堆 栈 详 解
堆 栈 详 解

堆栈详解及其python实现

wiki定义

堆栈(抽象数据类型)

有关在会计中使用术语LIFO,请参阅LIFO(会计)。对于力量训练中使用术语下推,请参阅下推(练习)。

有关其他用途,请参阅堆栈(消歧)。

使用推送和弹出操作简单表示堆栈运行时。

在计算机科学中,堆栈是一种抽象数据类型,用作元素集合,具有两个主要操作:

push,它为集合添加了一个元素,以及

pop,删除最近添加的尚未删除的元素。

元素从堆栈中出现的顺序产生了它的替代名称LIFO(last in,first out)。另外,窥视操作可以在不修改堆栈的情况下访问顶部。[1]这种结构的名称“堆叠”来自于相互堆叠的一组物理项目的类比,这样可以轻松地将物品从堆叠顶部取出,同时获取物品堆叠深度可能需要先取下多个其他物品。[2]

被认为是线性数据结构,或者更抽象地是顺序集合,推送和弹出操作仅发生在结构的一端,称为堆栈的顶部。这使得可以将堆栈实现为单链表和指向顶部元素的指针。可以实现堆栈以具有有界容量。如果堆栈已满并且没有足够的空间来接受要推送的实体,则认为堆栈处于溢出状态。pop 操作从堆栈顶部删除一个项目。

需要堆栈来实现深度优先搜索。

非必要的操作

软件堆栈

堆栈和编程语言

硬件堆栈

堆栈的基本架构

堆叠在主内存中

堆叠在寄存器或专用存储器中

堆栈的应用

表达式评估和语法分析

编译时间内存管理

高效的算法

也可以看看

进一步阅读

另见:Jan?ukasiewicz§工作

Stacks于1946年进入计算机科学文献,当时Alan M. Turing使用术语“埋葬”和“unbury”作为从子程序调用和返回的手段。[3]子程序已于1945年在Konrad Zuse的Z4中实施。

克劳斯·萨梅尔森和弗里德里希·L·包尔的慕尼黑工业大学提出这个构想于1955年并于1957年申请了专利,[4]和1988年3月鲍尔收到的计算机先驱奖为堆原理发明。[5]同样的概念是由澳大利亚人Charles Leonard Hamblin在1954年上半年独立开发的。[6]

堆栈通常类似于自助餐厅中的弹簧加载的堆叠板来描述。[7]?[2]?[8]?清洁板放在堆叠的顶部,向下推动任何已经存在的板。当从堆叠中移除板时,其下方的板突然弹出以成为新的顶部。

非必要的操作

在许多实现中,堆栈具有比“推”和“弹出”更多的操作。一个例子是“堆栈顶部”或“peek”,它观察最顶层的元素而不将其从堆栈中移除。

[9]由于这可以通过具有相同数据的“pop”和“push”来完成,因此不是必需的。如果堆栈为空,则在“堆栈顶部”操作中可能发生下溢情况,与“pop”相同。此外,实现通常具有仅返回堆栈是否为空的函数。

软件堆栈

数组可用于实现(有界)堆栈,如下所示。第一个元素(通常在零偏移处)是底部,导致array[0]第一个元素被压入堆栈并且最后一个元素弹出。程序必须跟踪堆栈的大小(长度),使用记录到目前为止推送的项目数的变量top,因此指向要插入下一个元素的数组中的位置(假设为零)基于指数的约定)。因此,堆栈本身可以有效地实现为三元素结构:结构堆栈:

maxsize:整数

顶部:整数

items:项目数组

procedure initialize(stk:stack,size:integer):

stk.items←新的大小项目数组,最初为空

stk.maxsize←大小

stk.top←0

检查溢出后,push操作会添加一个元素并递增顶部索引:

procedure push(stk:stack,x:item):

如果 stk.top = stk.maxsize:

报告溢出错误

stk.items [stk.top]←x

stk.top←stk.top + 1

类似地,pop在检查下溢后递减顶部索引,并返回先前最顶层的项:procedure pop(stk:stack):

如果 stk.top = 0:

报告下溢错误

stk.top←stk.top - 1

r←stk.items [stk.top]

使用动态数组,可以实现可以根据需要增长或缩小的堆栈。堆栈的大小就是动态数组的大小,这是一个非常有效的堆栈实现,因为向动态数组末尾添加项目或从中移除项目需要分摊O(1)时间。

实现堆栈的另一个选择是使用单链表。然后堆栈是指向列表“头部”的指针,可能还有一个计数器来跟踪列表的大小:

结构框架:

下一个:框架或零

结构堆栈:

头:框架或零

大小:整数

procedure initialize(stk:stack):

stk.head←无

stk.size←0

推送和弹出项目发生在列表的头部;?在此实现中无法溢出(除非内存耗尽):

程序推送(stk:stack,x:item):

newhead←新框架

newhead.data←x

newhead.next←stk.head

stk.head←newhead

stk.size←stk.size + 1

procedure pop(stk:stack):

如果 stk.head = nil:

报告下溢错误

r←stk.head.data

stk.head←stk.head.next

stk.size←stk.size - 1

堆栈和编程语言

某些语言(如Perl,LISP,JavaScript和Python)使堆栈操作在其标准列表-数组类型上可以推送和弹出。有些语言,特别是Forth系列中的语言(包括PostScript),是围绕语言定义的堆栈设计的,这些堆栈直接

由程序员可见并由程序员操纵。

以下是在Common Lisp中操作堆栈的示例(“?”是Lisp解释器的提示;不以“?”?开头的行是解释器对表达式的响应):

;; 设置变量“stack”

;; 得到顶部(最左边)的元素,应该修改堆栈

;; 检查堆栈的值

;; 将新顶部推入堆栈

一些C ++标准库容器类型具有带LIFO语义的push_back和pop_back 操作;?此外,堆栈模板类调整现有容器以提供仅具有推-弹操作的受限API。PHP有一个SplStack类。Java的库包含一个专门化的类。以下是使用该类的Java语言示例程序。?StackVector

java.util。*

StackDemo

--在堆栈

插入“A” 。

--在堆栈

插入“B” 。

--插入“C”

--在堆栈

插入“D” 。

的println

--打印堆栈顶部(“D”)

--删除顶部(“D”)

--删除下一个顶部(“C”)

硬件堆栈

架构级别的堆栈的常见用途是作为分配和访问存储器的手段。

堆栈的基本架构

典型的堆栈,用于存储嵌套过程调用的本地数据和调用信息(不一定是嵌套过程)。这个堆栈从它的起源向下增长。堆栈指针指向堆栈上当前最顶层的数据。推送操作递减指针并将数据复制到堆栈;?弹出操作从堆栈中复制数据,然后递增指针。程序中调用的每个过程通过将过程信息推送到堆栈中来存储过程返回信息(黄色)和本地数据(其他颜色)。这种类型的堆栈实现非常常见,但它容易受到缓冲区溢出攻击(参见文本)。

典型的堆栈是具有固定原点和可变大小的计算机存储器区域。最初堆栈的大小为零。甲堆栈指针,通常以硬件寄存器的形式,指向堆栈上最近引用的位置;?当堆栈的大小为零时,堆栈指针指向堆栈的原点。

适用于所有堆栈的两个操作是:

一个推操作,其中一个数据项被放置在该位置所指向堆栈指针,并且在堆栈指针的地址由数据项的大小调节;

一个弹出或拉操作:在当前位置的数据项指向堆栈指针被除去,堆栈指针由数据项的大小进行调整。

堆栈操作的基本原理有很多变化。每个堆栈在内存中都有一个固定的位置。随着数据项被添加到堆栈中,堆栈指针被移位以指示堆栈的当前范围,该范围从原点扩展。

堆栈指针可以指向堆栈的原点或者指向原点上方或下方的有限范围的地址(取决于堆栈增长的方向);?但是,堆栈指针不能跨越堆栈的原点。换句话说,如果堆栈的原点位于地址1000并且堆栈向下增长(朝向地址999,998等),则堆栈指针绝不能增加超过1000(到1001,1002等)。如果堆栈上的弹出操作导致堆栈指针移过堆栈的原点,则会发生堆栈下溢。如果推送操作导致堆栈指针递增或递减超出堆栈的最大范围,则发生堆栈溢出。

某些严重依赖堆栈的环境可能会提供其他操作,例如:

重复:弹出顶部项目,然后再次按下(两次),以便前一个顶部项目的附加副本现在位于顶部,原始项目位于其下方。

Peek:检查(或返回)最顶层的项目,但堆栈指针和堆栈大小不会改变(意味着该项目保留在堆栈中)。这在许多文章中也称为顶级操作。

交换或交换:堆栈交换位置上的两个最顶层项目。

旋转(或滚动):n个最顶部的项目以旋转方式在堆栈上移动。例如,如果n?= 3,则堆栈上的项目1,2和3分别移动到堆栈上的位置2,3和1。此操作的许多变体都是可能的,最常见的是左旋转和右旋。

堆栈通常是从底部向上可视化的(如真实世界的堆栈)。它们也可以从左到右可视化,使“最顶层”变得“最右边”,甚至从上到下增长。重要的特征是堆栈的底部处于固定位置。本节中的插图是从上到下增长可视化的示例:顶部(28)是堆栈“底部”,因为堆栈“顶部”(9)是项目被推送或弹出的位置。

甲右旋转将第一元素移动到该第三位置时,第二到第一和第三至第二。

以下是此过程的两个等效可视化:

苹果香蕉

香蕉===右旋==黄瓜

黄瓜苹果

黄瓜苹果

香蕉===左转==黄瓜

苹果香蕉

堆栈通常由计算机中的一块存储器单元表示,其中“底部”位于固定位置,堆栈指针保持堆栈中当前“顶部”单元的地址。无论堆栈是实际朝向较低的存储器地址还是朝向较高的存储器地址增长,都使用顶部和底部术语。

将项目推入堆栈会根据项目的大小调整堆栈指针(递减或递增,具体取决于堆栈在内存中的增长方向),将其指向下一个单元格,并将新的顶部项目复制到堆栈区域。再次取决于确切的实现,在推送操作结束时,堆栈指针可以指向堆栈中的下一个未使用的位置,或者它可以指向堆栈中的最顶层的项目。如果堆栈指向当前最顶层的项,则在将新项目推入堆栈之前,将更新堆栈指针;?如果它指向堆栈中的下一个可用位置,则在将新项目推入堆栈后将更新它。

弹出堆栈只是推动的反过来。删除堆栈中最顶层的项目,并按照与推送操作中使用的相反的顺序更新堆栈指针。

堆叠在主内存中

许多CISC类型的CPU设计,包括x86,Z80和6502,都有一个专用寄

存器,用作调用堆栈栈指针,带有专用的call,return,push和pop指令,可以隐式更新专用寄存器,从而提高代码密度。一些CISC处理器,如PDP-11和68000,也具有用于实现堆栈的特殊寻址模式,通常还具有半专用堆栈指针(例如68000中的A7)。相比之下,大多数RISC?CPU设计没有专用的堆栈指令,因此大多数(如果不是全部)寄存器可以根据需要用作堆栈指针。

堆叠在寄存器或专用存储器中

主要文章:堆叠机器

所述?的x87?浮点架构是一组组织成堆叠,其中直接访问各个寄存器(相对当前顶部)也是可能的寄存器的一个例子。与基于堆栈的计算机一样,将堆栈顶部作为隐式参数允许小的机器代码占用空间,同时充分利用总线?带宽和代码高速缓存,但它也阻止了处理器允许的某些类型的优化随机访问所有(两个或三个)操作数的寄存器文件。堆栈结构也使用寄存器重命名进行超标量实现(for推测执行)稍微更复杂的实施,尽管它仍然是可行的,如举例说明由现代的x87实现。

Sun SPARC,AMD Am29000和Intel i960都是在寄存器堆栈中使用寄存器窗口的架构示例,作为避免将慢速主存储器用于函数参数和返回值的另一种策略。

还有许多小型微处理器直接在硬件中实现堆栈,而一些微控制器具有不能直接访问的固定深度堆栈。例如PIC微控制器,Computer Cowboys?MuP21,Harris RTX系列和Novix?NC4016。许多基于堆栈的微处理器用于在微码级实现编程语言Forth。堆栈也被用作许多大型机和迷你

计算机的基础。这种机器被称为堆叠机,最着名的是Burroughs B5000。

堆栈的应用

表达式评估和语法分析

采用反向波兰表示法的计算器使用堆栈结构来保存值。表达式可以用前缀,后缀或中缀表示法表示,并且可以使用栈来完成从一种形式到另一种形式的转换。许多编译器在转换为低级代码之前使用堆栈来解析表达式,程序块等的语法。大多数编程语言都是无上下文的语言,允许使用基于堆栈的机器解析它们。

主要文章:回溯

堆栈的另一个重要应用是回溯。考虑一个在迷宫中找到正确路径的简单示例。从起点到目的地有一系列要点。我们从一点开始。要到达最终目的地,有几条路径。假设我们选择一个随机路径。在遵循某条路径之后,我们意识到我们选择的路径是错误的。因此,我们需要找到一种方法,以便我们可以返回到该路径的开头。这可以通过使用堆栈来完成。在堆栈的帮助下,我们记住了我们达到的目标。这是通过将该点推入堆栈来完成的。如果我们最终走错了路径,我们可以从堆栈中弹出最后一个点,从而返回到最后一个点并继续寻找正确的路径。这称为回溯。

回溯算法的典型示例是深度优先搜索,其查找可以从指定的起始顶点到达的图的所有顶点。回溯的其他应用涉及搜索代表优化问题的潜在解决方案的空间。分支和绑定是一种用于执行这种回溯搜索的技术,而无需在这样的空间中穷举搜索所有潜在的解决方案。

编译时间内存管理

主要文章:基于堆栈的内存分配和堆栈机器

许多编程语言都是面向堆栈的,这意味着它们定义了大多数基本操作(添加两个数字,打印一个字符),从堆栈中获取参数,并将任何返回值放回堆栈。例如,PostScript具有返回堆栈和操作数堆栈,还具有图形状态堆栈和字典堆栈。许多虚拟机也是面向堆栈的,包括p代码机和Java 虚拟机。

几乎所有调用约定?-?子例程接收其参数和返回结果的方式 - 使用特殊堆栈(“?调用堆栈?”)来保存有关过程-函数调用和嵌套的信息,以便切换到被调用函数的上下文并在调用结束时恢复到调用者函数。这些函数遵循调用者和被调用者之间的运行时协议来保存参数并在堆栈上返回值。堆栈是支持嵌套或递归函数调用的重要方法。编译器隐式使用这种类型的堆栈来支持CALL和RETURN语句(或它们的等价物),并且不由程序员直接操作。

一些编程语言使用堆栈来存储过程本地的数据。输入过程时,从堆栈中分配本地数据项的空间,并在过程退出时释放。在C编程语言以这种方式通常执行。对数据和过程调用使用相同的堆栈具有重要的安全隐患(见下文),程序员必须注意这一点,以避免在程序中引入严重的安全漏洞。

高效的算法

一些算法使用堆栈(与大多数编程语言的通常函数调用堆栈分开)作为用于组织其信息的主要数据结构。这些包括:

格雷厄姆扫描,一种二维点系统的凸包算法。输入子集的凸包保持在堆栈中,当将新点添加到船体时,该堆栈用于查找和移除边界中的凹陷。

[10]

用于查找单调矩阵的行最小值的SMAWK算法的一部分使用与Graham扫描类似的方式的堆栈。[11]

所有最接近的较小值,即对于数组中的每个数字,找到最接近的前一个数字的问题。针对该问题的一种算法使用堆栈来维护候选者的集合以获得最接近的较小值。对于数组中的每个位置,将弹出堆栈,直到在其顶部找到较小的值,然后将新位置中的值压入堆栈。[12]

在最近邻算法链,对于方法凝聚层次聚类基于维持堆叠簇,其中的每一个是其在栈上前身最近邻。当此方法找到一对相互最近邻居的聚类时,会弹出并合并它们。[13]

某些计算环境使用堆栈的方式可能使它们容易受到安全漏洞和攻击。在这种环境中工作的程序员必须特别小心,以避免这些实现的陷阱。

恶意方可以通过向不检查输入长度的程序提供过大的数据输入来尝试利用此类实现的堆栈粉碎攻击。这样的程序可以将数据完整地复制到堆栈上的位置,并且这样做可以改变已经调用它的过程的返回地址。攻击者可以尝试查找可以提供给此类程序的特定类型的数据,以便重置当前过程的返回地址以指向堆栈本身内的区域(以及攻击者提供的数据内),其中包含执行未授权操作的指令。

python实现

# 后进先出

class Stack():

def __init__(self,size):

self.size=size

self.stack=[]

self.top=-1

def push(self,x):# 入栈之前检查栈是否已满 if self.isfull():

print("stack is full")

self.stack.append(x)

self.top=self.top+1

def pop(self):# 出栈之前检查栈是否为空

if self.isempty():

print("stack is empty")

self.top=self.top-1

self.stack.pop()

def isfull(self):

return self.top+1 == self.size

def isempty(self):

return self.top == '-1'

def showStack(self):

print(self.stack)

s=Stack(10)

for i in range(6):

s.push(i)

s.showStack()

for i in range(3):

s.showStack()

类中有top属性,用来指示栈的存储情况,初始值为1,一旦插入一个元素,其值加1,利用top的值乐意判定栈是空还是满。

执行时先将0,1,2,3,4,5依次入栈,然后删除栈顶的前三个元素sq-rear = (sq-rear + 1) % MAXSIZE;

? System.out.println(str1==str2);

private AtomicReferenceNodeV top = new AtomicReference();

图6 example1.c编译后生成的汇编程序example1.s

System.out.println(str1==str2); -- false

堆栈首部是低地址,堆栈向高地址增长。栈指针总是指向下一个将要放入数据的空位置。

static STACK_TYPE stack[STACK_SIZE];

对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。如以下代码:

category的activity将被标记为task的入口。带有这两个标记的activity将会显示在应用程序启动器(application launcher)中。

_CRTIMP int (__cdecl *printf)(const char *, .); --定义STL函数printf

操作系统内存管理复习过程

操作系统内存管理

操作系统内存管理 1. 内存管理方法 内存管理主要包括虚地址、地址变换、内存分配和回收、内存扩充、内存共享和保护等功能。 2. 连续分配存储管理方式 连续分配是指为一个用户程序分配连续的内存空间。连续分配有单一连续存储管理和分区式储管理两种方式。 2.1 单一连续存储管理 在这种管理方式中,内存被分为两个区域:系统区和用户区。应用程序装入到用户区,可使用用户区全部空间。其特点是,最简单,适用于单用户、单任务的操作系统。CP/M和 DOS 2.0以下就是采用此种方式。这种方式的最大优点就是易于管理。但也存在着一些问题和不足之处,例如对要求内

存空间少的程序,造成内存浪费;程序全部装入,使得很少使用的程序部分也占用—定数量的内存。 2.2 分区式存储管理 为了支持多道程序系统和分时系统,支持多个程序并发执行,引入了分区式存储管理。分区式存储管理是把内存分为一些大小相等或不等的分区,操作系统占用其中一个分区,其余的分区由应用程序使用,每个应用程序占用一个或几个分区。分区式存储管理虽然可以支持并发,但难以进行内存分区的共享。 分区式存储管理引人了两个新的问题:内碎片和外碎片。 内碎片是占用分区内未被利用的空间,外碎片是占用分区之间难以利用的空闲分区(通常是小空闲分区)。 为实现分区式存储管理,操作系统应维护的数据结构为分区表或分区链表。表中各表项一般包括每个分区的起始地址、大小及状态(是否已分配)。

分区式存储管理常采用的一项技术就是内存紧缩(compaction)。 2.2.1 固定分区(nxedpartitioning)。 固定式分区的特点是把内存划分为若干个固定大小的连续分区。分区大小可以相等:这种作法只适合于多个相同程序的并发执行(处理多个类型相同的对象)。分区大小也可以不等:有多个小分区、适量的中等分区以及少量的大分区。根据程序的大小,分配当前空闲的、适当大小的分区。 优点:易于实现,开销小。 缺点主要有两个:内碎片造成浪费;分区总数固定,限制了并发执行的程序数目。 2.2.2动态分区(dynamic partitioning)。 动态分区的特点是动态创建分区:在装入程序时按其初始要求分配,或在其执行过程中通过系统调用进行分配或改变分区大小。与固定分区相比较其优点是:没有内碎

计算机算法设计与分析习题和答案解析

《计算机算法设计与分析》习题及答案 一.选择题 1、二分搜索算法是利用(A )实现的算法。 A、分治策略 B、动态规划法 C、贪心法 D、回溯法 2、下列不是动态规划算法基本步骤的是(A )。 A、找出最优解的性质 B、构造最优解 C、算出最优解 D、定义最优解 3、最大效益优先是( A )的一搜索方式。 A、分支界限法 B、动态规划法 C、贪心法 D、回溯法 4. 回溯法解旅行售货员问题时的解空间树是( A )。 A、子集树 B、排列树 C、深度优先生成树 D、广度优先生成树 5.下列算法中通常以自底向上的方式求解最优解的是( B )。 A、备忘录法 B、动态规划法 C、贪心法 D、回溯法 6、衡量一个算法好坏的标准是(C )。 A 运行速度快 B 占用空间少 C 时间复杂度低 D 代码短 7、以下不可以使用分治法求解的是(D )。 A 棋盘覆盖问题 B 选择问题 C 归并排序 D 0/1背包问题 8. 实现循环赛日程表利用的算法是( A )。 A、分治策略 B、动态规划法 C、贪心法 D、回溯法 9.下面不是分支界限法搜索方式的是( D )。 A、广度优先 B、最小耗费优先 C、最大效益优先 D、深度优先 10.下列算法中通常以深度优先方式系统搜索问题解的是( D )。 A、备忘录法 B、动态规划法 C、贪心法 D、回溯法 11.备忘录方法是那种算法的变形。(B ) A、分治法 B、动态规划法 C、贪心法 D、回溯法 12.哈夫曼编码的贪心算法所需的计算时间为( B )。 A、O(n2n) B、O(nlogn) C、O(2n) D、O(n) 13.分支限界法解最大团问题时,活结点表的组织形式是( B )。 A、最小堆 B、最大堆 C、栈 D、数组 14.最长公共子序列算法利用的算法是( B )。 A、分支界限法 B、动态规划法 C、贪心法 D、回溯法 15.实现棋盘覆盖算法利用的算法是( A )。 A、分治法 B、动态规划法 C、贪心法 D、回溯法 16.下面是贪心算法的基本要素的是( C )。 A、重叠子问题 B、构造最优解 C、贪心选择性质 D、定义最优解 17.回溯法的效率不依赖于下列哪些因素( D ) A.满足显约束的值的个数 B. 计算约束函数的时间 C.计算限界函数的时间 D. 确定解空间的时间 18.下面哪种函数是回溯法中为避免无效搜索采取的策略( B ) A.递归函数 B.剪枝函数C。随机数函数 D.搜索函数 19. ( D )是贪心算法与动态规划算法的共同点。

堆与栈

栈是由编译器在需要的时分配的,不需要时自动清除的变量存储区。里面的变量通常是局部变量、函数参数等。堆是有malloc()函数(C++语言为new运算符)分配为内存快,内存的释放由程序员手动控制,在C语言为free()完成(C++中为deleted)。堆和栈的主要区别有以下几点: (1)管理方式不同 栈编译器自动管理,无需程序员手工控制;而堆空间的申请释放工作由程序员控制,容易产生内存泄漏。 (2)空间的大小不同 栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先设定好,当申请的空间超过栈的剩余空间时,将提示溢出。因此,用户能从栈获得空间较小。 堆是向高地址扩展的数据结构,是不连续的内存区域。因为系统是用链表来存储空闲内存地址的,且链表的遍历方向是由低地址向高地址。由此可见,堆获得空间较灵活,也较大。栈中元素都是一一对应的,不会存在一个内存块从中弹出的情况。 (3)是否产生碎片 对于栈来讲,频繁的malloc/free(new/delete)势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低(虽然程序在退出后操作系统会对内存进行回收管理)。对于栈来讲,则不会存在这个问题。 (4) 增长方向不同 堆的增长方向是向上的,即向着内存地址增加的方向。栈的增长方向是向下的,即向着内存地址减小的方向。 (5)分配方式不同 堆都是程序中由malloc()函数动态申请分配并由free()函数释放的;栈的分配和释放是由编译器完成的,栈的动态分配由alloca()函数完成,但是栈的动态分配和对不同,它的动态分配是由编译器进行申请和释放的,无需手工实现。 (6)分配效率不同 栈是由机器系统提供的数据结构,计算机会在底层对栈提供支持;分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令进行。堆则是C函数库提供的,它的机制很复杂,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大的空间,如果没有足够大的空间(可能是由于内存碎片太多),就需要操作系统来重新整理内存空间,这样就有机会分到足够大小的内存,然后返回。 显然堆的效率要比栈低得多。 可执行代码运行时内存结构结构: (1)代码区(text segment)。代码区指令根据程序设计流程依次执行,对于顺序指令,则只会执行一次(每个进程),如果反复,则需要使用跳转指令,如果进行递归,则需要借助栈来实现。 代码区的指令包括操作码和要操作的对象(或对象地址引用)。如果是里技术(及具体的数值),将直接包含在代码中;如果是局部变量,将在栈区分配空间。然后引用该数的地址;如果是BSS去和数据区,在代码中同样是引用该数的地址。

linux内存管理子系统 笔记

4-4 linux内存管理子系统 4-4-1 linux内存管理(参考课件) 物理地址:cpu地址总线上寻址物理内存的地址信号,是地址变换的最终结果 逻辑地址:程序代码经过编译后,出现在汇编程序中的地址(程序设计时使用的地址) 线性地址:又名虚拟地址,32位cpu架构下4G地址空间 CPU要将一个逻辑地址转换为物理地址,需要两步: 1、首先CPU利用段式内存管理单元,将逻辑地址转换成线性地址; 2、再利用页式内存管理单元,把线性地址最终转换为物理地址 相关公式: 逻辑地址=段基地址+段内偏移量(段基地址寄存器+段偏移寄存器)(通用的) 16位CPU:逻辑地址=段基地址+段内偏移量(段基地址寄存器+段偏移寄存器) 线性地址=段寄存器的值×16+逻辑地址的偏移部分 物理地址=线性地址(没有页式管理) 32位CPU:逻辑地址=段基地址+段内偏移量(段基地址寄存器+段偏移寄存器) 线性地址=段寄存器的值+逻辑地址的偏移部分 物理地址<——>线性地址(mapping转换) ARM32位:逻辑地址=段基地址+段内偏移量(段基地址寄存器+段偏移寄存器) 逻辑地址=段内偏移量(段基地址为0) 线性地址=逻辑地址=段内偏移量(32位不用乘以32) 物理地址<——>线性地址(mapping转换) ************************!!以下都是x86模式下!!********************************* 一、段式管理 1.1、16位CPU:(没有页式管理) 1.1.1、段式管理的由来: 16位CPU内部有20位地址总线,可寻址2的20次方即1M的内存空间,但16位CPU 只有16位的寄存器,因此只能访问2的16次方即64K。因此就采用了内存分段的管理模式,在CPU内部加入了段寄存器,这样1M被分成若干个逻辑段,每个逻辑段的要求如下: 1、逻辑段的起始地址(段地址)必须是16的整数倍,即最后4个二进制位须全是0 (因此不必保存)。 2、逻辑段的最大容量为64K。 1.1.2、物理地址的形成方式: 段地址:将段寄存器中的数值左移4位补4个0(乘以16),得到实际的段地址。 段偏移:在段偏移寄存器中。 1)逻辑地址=段基地址+段内偏移量(段基地址寄存器+段偏移寄存器) 2)由逻辑地址得到物理地址的公式为:(因为没有页式管理,所以这一步就得到了物理地址)物理地址PA=段寄存器的值×16+逻辑地址的偏移部分(注意!!)(段与段可能会重叠)

堆 栈 详 解

堆栈详解及其python实现 wiki定义 堆栈(抽象数据类型) 有关在会计中使用术语LIFO,请参阅LIFO(会计)。对于力量训练中使用术语下推,请参阅下推(练习)。 有关其他用途,请参阅堆栈(消歧)。 使用推送和弹出操作简单表示堆栈运行时。 在计算机科学中,堆栈是一种抽象数据类型,用作元素集合,具有两个主要操作: push,它为集合添加了一个元素,以及 pop,删除最近添加的尚未删除的元素。 元素从堆栈中出现的顺序产生了它的替代名称LIFO(last in,first out)。另外,窥视操作可以在不修改堆栈的情况下访问顶部。[1]这种结构的名称“堆叠”来自于相互堆叠的一组物理项目的类比,这样可以轻松地将物品从堆叠顶部取出,同时获取物品堆叠深度可能需要先取下多个其他物品。[2] 被认为是线性数据结构,或者更抽象地是顺序集合,推送和弹出操作仅发生在结构的一端,称为堆栈的顶部。这使得可以将堆栈实现为单链表和指向顶部元素的指针。可以实现堆栈以具有有界容量。如果堆栈已满并且没有足够的空间来接受要推送的实体,则认为堆栈处于溢出状态。pop 操作从堆栈顶部删除一个项目。

需要堆栈来实现深度优先搜索。 非必要的操作 软件堆栈 堆栈和编程语言 硬件堆栈 堆栈的基本架构 堆叠在主内存中 堆叠在寄存器或专用存储器中 堆栈的应用 表达式评估和语法分析 编译时间内存管理 高效的算法 也可以看看 进一步阅读 另见:Jan?ukasiewicz§工作 Stacks于1946年进入计算机科学文献,当时Alan M. Turing使用术语“埋葬”和“unbury”作为从子程序调用和返回的手段。[3]子程序已于1945年在Konrad Zuse的Z4中实施。 克劳斯·萨梅尔森和弗里德里希·L·包尔的慕尼黑工业大学提出这个构想于1955年并于1957年申请了专利,[4]和1988年3月鲍尔收到的计算机先驱奖为堆原理发明。[5]同样的概念是由澳大利亚人Charles Leonard Hamblin在1954年上半年独立开发的。[6]

C语言堆和栈

在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到。 但对于很多的初学着来说,堆栈是一个很模糊的概念。堆栈:一种数据结构、一个在程序运 行时用于存放的地方,这可能是很多初学者的认识,因为我曾经就是这么想的和汇编语言中 的堆栈一词混为一谈。我身边的一些编程的朋友以及在网上看帖遇到的朋友中有好多也说不 清堆栈,所以我想有必要给大家分享一下我对堆栈的看法,有说的不对的地方请朋友们不吝 赐教,这对于大家学习会有很大帮助。 首先在数据结构上要知道堆栈,尽管我们这么称呼它,但实际上堆栈是两种数据结构:堆和栈。 堆和栈都是一种数据项按序排列的数据结构。 我们先从大家比较熟悉的栈说起吧,它是一种具有后进先出性质的数据结构,也就是说 后存放的先取,先存放的后取。这就如同我们要取出放在箱子里面底下的东西(放入的比较 早的物体),我们首先要移开压在它上面的物体(放入的比较晚的物体)。而堆就不同了,堆 是一种经过排序的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是 指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。由于 堆的这个特性,常用来实现优先队列,堆的存取是随意,这就如同我们在图书馆的书架上取 书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的 书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。 然而我要说的重点并不在这,我要说的堆和栈并不是数据结构的堆和栈,之所以要说数 据结构的堆和栈是为了和后面我要说的堆区和栈区区别开来,请大家一定要注意。 下面就说说C语言程序内存分配中的堆和栈,这里有必要把内存分配也提一下,大家不 要嫌我啰嗦,一般情况下程序存放在Rom或Flash中,运行时需要拷到内存中执行,内存会分 别存储不同的信息,如下图所示:

Solaris 8内存管理机制研究

Solaris 8内存管理机制研究 吴海燕 戚丽 冯珂 摘 要:寻找性能瓶颈是性能分析中的一项重要任务,内存瓶颈的表现并不像CPU或磁盘那样直接,本文通过对Solaris 8内存管理机制的研究,给出了寻找Solaris 8系统内存瓶颈的方法。 关键词:Solaris 8,内存管理,性能优化 一、问题的提出 清华大学计算机与信息管理中心数据中心现有服务器近百台,其中包括了SUN Fire 15000、SUN Enterprise 5500、SUN Enterprise 5000等大型SUN服务器,Solaris 8是主流操作系统。为了对服务器的资源(如CPU、内存、磁盘、网络)的使用情况进行长期监控,建立性能优化(performance tuning)的基准值,我们开发了一套脚本程序定时采集系统运行参数。在长期的监控中,我们发现Solaris 8系统的空闲内存(freemem)呈现一个有趣的变化规律,如图1所示: 图1 空闲内存(freemem)变化图 图1是某Solaris 8系统(在下文中我们称之为15k-a)自2003年2月份以来的freemem 变化情况,横坐标是时间,纵坐标是freemem的数量,以8K字节为单位。15k-a配置是10路Super SPARCIII CPU,10GB物理内存。从上图可以看到在正常运行时,freemem应该是比较稳定的,15k-a主要是运行数据库,数据库在运行时会占用2G内存作为SGA区使用,因此在通常的负载下,freemem保持在6~7G之间是比较正常的。稳定一段时间后,

15k-a的freemem会持续走低,直到最低值,约为18893×8KMB,然后系统开始回收内存,我们就会看到freemem数量急剧上升。freemem的陡降都发生在凌晨1:00之后,检查系统作业发现每天1:00都会有一个数据库备份脚本开始运行:首先是用“exp”命令给数据库做逻辑备份,然后用“cp”命令把备份出来的文件拷贝到后备存储上。这两个命令都是正常退出,没有任何报错。开始时我们曾怀疑是有内存泄漏,当某一天freemem大幅攀升时,此怀疑被解除了,因为如果有内存泄漏,系统是无法将内存回收回来的。 对于一个物理内存为10GB的系统来说,如果空闲内存(freemem)真的减少到不到二百兆,那将存在着严重的问题。但奇怪的是系统的CPU使用率一直很低,所有进程的反应也很快,系统没有任何资源匮乏的迹象。如何解释这些问题呢,为此我们对Solaris 2.x 的内存管理机制进行了研究。 二、Solaris的内存管理机制 Solaris 8的内存管理为虚拟内存管理。[1]简单地说,虚拟内存就是进程看到比它实际使用的物理内存多得多的内存空间,对于64位的Solaris 8操作系统,进程可以通过8K 大小的段寻址访问2的64次方字节的内存空间,这种8K的段被称为页(page)。传统的UNIX通过进程(pagedaemon)完成虚拟地址和物理地址间的转换,在Solaris中这些是通过一个硬件-MMU(Memory Management Unit)-来实现的。在多处理器系统中,每个CPU 都有自己的MMU。Solaris 8的虚拟存储体系由系统寄存器、CPU CACHE、主存(RAM,物理内存)、外存(磁盘、磁带等)构成。 有两个基本的虚拟内存系统管理模型[2]:交换(swapping)和按需换页(demand paged)模型。交换模型的内存管理粒度是用户进程,当内存不足时,最不活跃的进程被交换出内存(swapping out)。按需换页模型的内存管理粒度是页(page),当内存匮乏时,只有最不经常使用的页被换出。Solaris 8结合使用了这两种内存管理模型,在通常情况下使用按需换页模型,当内存严重不足时,使用交换模型来进行内存释放。 与传统UNIX系统相比,Solaris虚拟内存系统的功能要丰富得多,它负责管理所有与I/O和内存相关的对象,包括内核、用户应用程序、共享库和文件系统。传统的UNIX系统V(System V)使用一个单独的缓冲区来加速文件系统的I/O, Solaris 8则使用虚拟内存系统来管理文件系统的缓存,系统的所有空闲内存都可以被用来做为文件I/O缓存,因为RAM的访问速度比磁盘快得多,所以这样做带来的性能提高是可观的。这也意味着在存在大量文件系统I/O的系统上,空闲内存的数量几乎是0。 了解系统内存被分配到了什么地方,系统在什么情况下进行内存整理是系统管理的重

计算机组成原理堆栈

堆栈是计算机系统中的一个重要概念,也是理解微型计算机组成的一个基础概念。堆栈是一种存储部件,即数据的写入跟读出不需要提供地址,而是根据写入的顺序决定读出的顺序。 堆栈也是一种数据结构。有一个地址指针总指向最后一个压入堆栈的数据所在的数据单元,存放这个地址指针的寄存器就叫做堆栈指示器。数据一个一个地存入,这个过程叫做“压栈”。在压栈的过程中,每有一个数据压入堆栈,就放在和前一个单元相连的后面一个单元中,堆栈指示器中的地址自动加1。读取这些数据时,按照堆栈指示器中的地址读取数据,堆栈指示器中的地址数自动减1。这个过程叫做“出栈pop”。如此就实现了后进先出的原则。 一般的堆栈存储器由RAM、寄存器A,寄存器B构成。算术运算一般在寄存器A和寄存器B 之间进行,其中的数据可能来自于进栈的输入也可能来自栈堆的出栈。运算结果则会放入寄存器B中以待下步操作。 此次的课程设计做出的堆栈处理器,使其能与外部数据总线进行数据交换,且符合堆栈要求(先进后出),并能对存储的数据进行算术运算,且存储的数据的数据位不少于8位,通过数码管显示操作数据及运算结果(只有寄存器A、B 直接与外部总线进行数据交换,RAM只和寄存器B进行数据交换)。 关键词:堆栈,RAM, PUSH, POP , 寄存器A,寄存器B

一. 任务解析 (3) 二. 系统方案论证 (3) 2.1总体方案与比较论证 (3) 2.2 设计思路 (4) 2.3系统原理与结构 (4) 2.3.1 系统框图 (4) 2.3.2 系统结构框图 (5) 2.3.3堆栈电路图 (5) 三. 设计过程 (6) 3.1堆栈存储器的设计 (6) 3.1.1堆栈存储器设计原理及仿真结果 (6) 3.2对数据进行算术运算的设计及仿真 (7) 3.2.1 对存储器中数据进行加法运算 (7) 3.2.2 对存储器中数据进行减法运算 (8) 3.2.3 对存储器中数据进行乘法运算 (8) 3.2.4 对存储器中数据进行除法运算 (9) 四. 总结 (9) 4.1遇到的问题及解决方案 (9) 4.2设计心得 (10) 4.3参考文献 (10)

java中堆和栈的区别

Java中堆与栈的区别 简单的说: Java把内存划分成两种:一种是栈内存,一种是堆内存。 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。 当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。 堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。 1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java 自动管理栈和堆,程序员不能直接地设置栈或堆。 2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。 3. Java中的数据类型有两种。 一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出

JVM原理以及JVM内存管理机制

一、 JVM简介 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.exe来完成, 首先来说一下JVM工作原理中的jdk这个东西, .JVM 在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,提供一个完整的Java运行环境,因此也就虚拟计算机. 操作系统装入JVM是通过jdk中Java.exe来完成。 通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例 4.调用JNIEnv实例装载并处理class类。 对于JVM自身的物理结构,我们可以从下图了解:

JVM的一个重要的特征就是它的自动内存管理机制,在执行一段Java代码的时候,会把它所管理的内存划分 成几个不同的数据区域,其中包括: 1. 程序计数器,众所周知,JVM的多线程是通过线程轮流切换并 分配CPU执行时间的方式来实现的,那么每一个线程在切换 后都必须记住它所执行的字节码的行号,以便线程在得到CPU 时间时进行恢复,这个计数器用于记录正在执行的字节码指令的地址,这里要强调的是“字节码”,如果执行的是Native方法,那么这个计数器应该为null; 2.

3. Java计算栈,可以说整个Java程序的执行就是一个出栈入栈 的过程,JVM会为每一个线程创建一个计算栈,用于记录线程中方法的调用和变量的创建,由于在计算栈里分配的内存出栈后立即被抛弃,因此在计算栈里不存在垃圾回收,如果线程请求的栈深度大于JVM允许的深度,会抛出StackOverflowError 异常,在内存耗尽时会抛出OutOfMemoryError异常; 4. Native方法栈,JVM在调用操作系统本地方法的时候会使用到 这个栈; 5. Java堆,由于每个线程分配到的计算栈容量有限,对于可能会 占据大量内存的对象,则会被分配到Java堆中,在栈中包含了指向该对象内存的地址;对于一个Java程序来说,只有一个Java堆,也就是说,所有线程共享一个堆中的对象;由于Java堆不受线程的控制,如果在一个方法结束之后立即回收这个方法使用到的对象,并不能保证其他线程是否正在使用该对象;因此堆中对象的回收由JVM的垃圾收集器统一管理,和某一个线程无关;在HotSpot虚拟机中Java堆被划分为三代:o新生代,正常情况下新创建的对象会被分配到新生代,但如果对象占据的内存足够大以致超过了新生代的容量限 制,也可能被分配到老年代;新生代对象的一个特点是最 新、且生命周期不长,被回收的可能性高;

堆栈及静态数据区详解

堆、栈及静态数据区详解 五大内存分区 在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。 自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free 来结束自己的生命的。 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多) 明确区分堆与栈 在bbs上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决定拿他第一个开刀。 首先,我们举一个例子: void f() { int* p=new int[5]; } 这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,然后返回这块内存的首地址,放入栈中,他在VC6下的汇编代码如下: 00401028 push 14h 0040102A call operator new (00401060) 0040102F add esp,4 00401032 mov dword ptr [ebp-8],eax 00401035 mov eax,dword ptr [ebp-8]

堆与栈,静态变量和全局变量的区别

堆与栈,静态变量和全局变量的区别 堆与栈,静态变量和全局变量的区别 对和栈的主要的区别由以下几点: 1、管理方式不同; 2、空间大小不同; 3、能否产生碎片不同; 4、生长方向不同; 5、分配方式不同; 6、分配效率不同; 管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。 空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改: 打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。 注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。 碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。 生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。 分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

全面介绍Windows内存管理机制

全面介绍Windows内存管理机制及C++内存分配实例 文章整理: https://www.360docs.net/doc/1a9444147.html, 文章来源: 网络- - 本文背景: 在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用;根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制。 本文目的: 对Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。本文内容: 本文一共有六节,由于篇幅较多,故按节发表。 1.进程地址空间 1.1地址空间 ?32|64位的系统|CPU 操作系统运行在硬件CPU上,32位操作系统运行于32位CPU 上,64位操作系统运行于64位CPU上;目前没有真正的64位CPU。 32位CPU一次只能操作32位二进制数;位数多CPU设计越复杂,软件设计越简单。 软件的进程运行于32位系统上,其寻址位也是32位,能表示的空间是232=4G,范围从0x0000 0000~0xFFFF FFFF。 ?NULL指针分区 范围:0x0000 0000~0x0000 FFFF 作用:保护内存非法访问 例子:分配内存时,如果由于某种原因分配不成功,则返回空指针0x0000 0000;当用户继续使用比如改写数据时,系统将因为发生访问违规而退出。 那么,为什么需要那么大的区域呢,一个地址值不就行了吗?我在想,是不是因为不让8或16位的程序运行于32位的系统上呢?!因为NULL分区刚好范围是16的进程空间。 ?独享用户分区 范围:0x0001 0000~0x7FFE FFFF 作用:进程只能读取或访问这个范围的虚拟地址;超越这个范围的行为都 会产生违规退出。 例子: 程序的二进制代码中所用的地址大部分将在这个范围,所有exe 和dll文件都加载到这个。每个进程将近2G的空间是独享的。 注意:如果在boot.ini上设置了/3G,这个区域的范围从2G扩大为3G: 0x0001 0000~0xBFFE FFFF。 ?共享内核分区 范围:0x8000 0000~0xFFFF FFFF 作用:这个空间是供操作系统内核代码、设备驱动程序、设备I/O高速缓存、非页面内存池的分配、进程目表和页表等。 例子: 这段地址各进程是可以共享的。

C++中堆和栈的区别

C++中堆和栈的区别,自由存储区、全局/静态存储区和常量存储区 文章来自一个论坛里的回帖,哪个论坛记不得了! 在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。 自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的(初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域,同时未被初始化的对象存储区可以通过void*来访问和操纵,程序结束后由系统自行释放),在C++里面没有这个区分了,他们共同占用同一块内存区。 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多) 明确区分堆与栈 在bbs上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决定拿他第一个开刀。 首先,我们举一个例子: void f() { int* p=new int[5]; } 这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,然后返回这块内存的首地址,放入栈中,他在VC6下的汇编代码如下: 00401028 push 14h 0040102A call operator new (00401060) 0040102F add esp,4 00401032 mov dword ptr [ebp-8],eax 00401035 mov eax,dword ptr [ebp-8] 00401038 mov dword ptr [ebp-4],eax 这里,我们为了简单并没有释放内存,那么该怎么去释放呢?是delete p 么?澳,错了,应该是delete []p,这是为了告诉编译器:我删除的是一个数组,VC6就会根据相应的Cookie信息去进行释放内存的工作。 好了,我们回到我们的主题:堆和栈究竟有什么区别? 主要的区别由以下几点: 1、管理方式不同; 2、空间大小不同; 3、能否产生碎片不同; 4、生长方向不同;

操作系统内存管理原理

内存分段和请求式分页 在深入i386架构的技术细节之前,让我们先返回1978年,那一年Intel 发布了PC处理器之母:8086。我想将讨论限制到这个有重大意义的里程碑上。如果你打算知道更多,阅读Robert L.的80486程序员参考(Hummel 1992)将是一个很棒的开始。现在看来这有些过时了,因为它没有涵盖Pentium处理器家族的新特性;不过,该参考手册中仍保留了大量i386架构的基本信息。尽管8086能够访问1MB RAM的地址空间,但应用程序还是无法“看到”整个的物理地址空间,这是因为CPU寄存器的地址仅有16位。这就意味着应用程序可访问的连续线性地址空间仅有64KB,但是通过16位段寄存器的帮助,这个64KB大小的内存窗口就可以在整个物理空间中上下移动,64KB逻辑空间中的线性地址作为偏移量和基地址(由16位的段寄存器给处)相加,从而构成有效的20位地址。这种古老的内存模型仍然被最新的Pentium CPU支持,它被称为:实地址模式,通常叫做:实模式。 80286 CPU引入了另一种模式,称为:受保护的虚拟地址模式,或者简单的称之为:保护模式。该模式提供的内存模型中使用的物理地址不再是简单的将线性地址和段基址相加。为了保持与8086和80186的向后兼容,80286仍然使用段寄存器,但是在切换到保护模式后,它们将不再包含物理段的地址。替代的是,它们提供了一个选择器(selector),该选择器由一个描述符表的索引构成。描述符表中的每一项都定义了一个24位的物理基址,允许访问16MB RAM,在当时这是一个很不可思议的数量。不过,80286仍然是16位CPU,因此线性地址空间仍然被限制在64KB。 1985年的80386 CPU突破了这一限制。该芯片最终砍断了16位寻址的锁链,将线性地址空间推到了4GB,并在引入32位线性地址的同时保留了基本的选择器/描述符架构。幸运的是,80286的描述符结构中还有一些剩余的位可以拿来使用。从16位迁移到32位地址后,CPU的数据寄存器的大小也相应的增加了两倍,并同时增加了一个新的强大的寻址模型。真正的32位的数据和地址为程序员带了实际的便利。事实上,在微软的Windows平台真正完全支持32位模型是在好几年之后。Windows NT的第一个版本在1993年7月26日发布,实现了真正意义上的Win32 API。但是Windows 3.x程序员仍然要处理由独立的代码和数据段构成的64KB内存片,Windows NT提供了平坦的4GB地址空间,在那儿可以使用简单的32位指针来寻址所有的代码和数据,而不需要分段。在内部,当然,分段仍然在起作用,就像我在前面提及的那样。不过管理段的所有责任都被移给了操作系统。

堆栈详解(数据与内存中的存储方式)

堆栈详解(数据与内存中的存储方式) char* r = "hello word!";char b[]="hello word!"*r = 'w';*b='w';其实应该是语法错误,可是VC++6.0没有警告或者错误,r指向的是文字常量区,此区域是编译的时候确定的,并且程序结束的时候自动释放的,*r = 'w';企图修改文字常量区引起错误,b的区别在于其空间是在栈上分配的,因此没有错误。const char* r = "hello word!";*r = 'w';一个由 c/C++编译的程序占用的内存分为以下几个部分1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。2、堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束后有系统释放4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放5、程序代码区—存放函数体的二进制代码。二、例子程序//main.cppint 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下,栈的

JVM内存分配(栈堆)与JVM回收机制

Java 中的堆和栈 简单的说: Java把内存划分成两种:一种是栈内存,一种是堆内存。 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。 当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。 堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。 在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。 引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。 具体的说: 栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。 Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。 栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。 栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义: int a = 3; int b = 3; 编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b 的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。 String是一个特殊的包装类数据。可以用: String str = new String("abc"); String str = "abc"; 两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。 而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str指向”abc”,如果已经有”abc”则直接令 str指向“abc”。 比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。 String str1 = "abc"; String str2 = "abc"; System.out.println(str1==str2); //true

相关文档
最新文档