第1 8章 堆 栈

对内存进行操作的第三个机制是使用堆栈。堆栈可以用来分配许多较小的数据块。例如,若要对链接表和链接树进行管理,最好的方法是使用堆栈,而不是第 1 5章介绍的虚拟内存操作方法或第1 7章介绍的内存映射文件操作方法。堆栈的优点是,可以不考虑分配粒度和页面边界之类的问题,集中精力处理手头的任务。堆栈的缺点是,分配和释放内存块的速度比其他机制要慢,并且无法直接控制物理存储器的提交和回收。

从内部来讲,堆栈是保留的地址空间的一个区域。开始时,保留区域中的大多数页面没有被提交物理存储器。当从堆栈中进行越来越多的内存分配时,堆栈管理器将把更多的物理存储器提交给堆栈。物理存储器总是从系统的页文件中分配的,当释放堆栈中的内存块时,堆栈管理器将收回这些物理存储器。

18.1 进程的默认堆栈

当进程初始化时,系统在进程的地址空间中创建一个堆栈。该堆栈称为进程的默认堆栈。

按照默认设置,该堆栈的地址空间区域的大小是 1 MB。但是,系统可以扩大进程的默认堆栈,使它大于其默认值。当创建应用程序时,可以使用 / H E A P链接开关,改变堆栈的1 M B默认区域大小。由于 D L L没有与其相关的堆栈,所以当链接 D L L时,不应该使用 / H E A P链接开关。

/ H E A P链接开关的句法如下:

/ H E A P:reserve[,commit]

许多Wi n d o w s函数要求进程使用其默认堆栈。例如, Windows 2000的核心函数均使用U n i c o d e字符和字符串执行它们的全部操作。如果调用Wi n d o w s函数的A N S I版本,那么该A N S I版本必须将A N S I字符串转换成U n i c o d e字符串,然后调用同一个函数的 U n i c o d e版本。为了进行字符串的转换,A N S I函数必须分配一个内存块,以便放置U n i c o d e版本的字符串。该内存块是从你的进程的默认堆栈中分配的。 Wi n d o w s的其他许多函数需要使用一些临时内存块,这些内存块是从进程的默认堆栈中分配的。

由于进程的默认堆栈可供许多Wi n d o w s函数使用,你的应用程序有许多线程同时调用各种Wi n d o w s函数,因此对默认堆栈的访问是顺序进行的。

单个进程可以同时拥有若干个堆栈。这些堆栈可以在进程的寿命期中创建和撤消。但是,

默认堆栈是在进程开始执行之前创建的,并且在进程终止运行时自动被撤消。不能撤消进程的默认堆栈。每个堆栈均用它自己的堆栈句柄来标识,用于分配和释放堆栈中的内存块的所有堆栈函数都需要这个堆栈句柄作为其参数。

可以通过调用G e t P r o c e s s H e a p函数获取你的进程默认堆栈的句柄:

HANDLE GetProcessHeap();

18.2 为什么要创建辅助堆栈

除了进程的默认堆栈外,可以在进程的地址空间中创建一些辅助堆栈。由于下列原因,你可能想要在自己的应用程序中创建一些辅助堆栈:

• 保护组件。

• 更加有效地进行内存管理。

• 进行本地访问。

• 减少线程同步的开销。

• 迅速释放。

下面让我们来详细说明每个原因。

18.2.1 保护组件

假如你的应用程序需要保护两个组件,一个是节点结构的链接表,一个是 B R A N C H结构的二进制树。你有两个源代码文件,一个是 L n k L s t . c p p,它包含负责处理

N O D E链接表的各个函数,另一个文件是B i n Tr e e . c p p,它包含负责处理

分支的二进制树的各个函数。

如果节点和分支一道存储在单个堆栈中,那么这个组合堆栈将类似图1 8 - 1所示的样子。

现在假设链接表代码中有一个错误,它使节点 1后面的8个字节不小心被改写了,从而导致分支 3中的数据被破坏。当B i n Tr e e . c p p文件中的代码后来试图遍历二进制树时,它将无法进行这项操作,因为它的内存已经被破坏。当然,这使你认为二进制树代码中存在一个错误,而实际上错误是在链接表代码中。由于不同类型的对象混合放在单个堆栈中,因此跟踪和确定错误将变得非常困难。

通过创建两个独立的堆栈,一个堆栈用于存放节点,另一个堆栈用于存放分支,就能够确定你的问题。你的链接表代码中的一个小错误不会破坏你的二进制树的完整性。反过来,二进制树中的小错误也不会影响链接表代码中的数据完整性。但是,你的代码中的错误仍然

可能导致对堆栈进行杂乱的内存写操作,不过出现这种情况的可能性很小。

18.2.2 更有效的内存管理

通过在堆栈中分配同样大小的对象,就可以更加有效地管理堆栈。例如,假设每个节点结构需要2 4字节,每个分支结构需要3 2字节。所有这些对象均从单个堆栈中分配。图 1 8 - 2显示了单个堆栈中已经分配的若干个节点和分支对象占满了这个堆栈。如果节点 2和节点4被释放,堆栈中的内存将变成许多碎片。这时,如果试图分配分支结构,那么尽管分支只需要3 2个字节,而实际上可以使用的有4 8个字节,但是分配仍将失败。

如果每个堆栈只包含大小相同的对象,那么释放一个对象后,另一个对象就可以恰好放入被释放的对象空间中。

18.2.3 进行本地访问

每当系统必须在R A M与系统的页文件之间进行 R A M页面的交换时,系统的运行性能就会受到很大的影响。如果经常访问局限于一个小范围地址的内存,那么系统就不太可能需要在 R A M与磁盘之间进行页面的交换。

所以,在设计应用程序的时候,如果有些数据将被同时访问,那么最好把它们分配在互相靠近的位置上。让我们回到链接表和二进制树的例子上来,遍历链接表与遍历二进制树之间并无什么关系。如果将所有的节点放在一起(放在一个堆栈中),就可以使这些节点位于相邻的页面上。实际上,若干个节点很可能恰好放入单个物理内存页面上。遍历链接表将不需要 C P U为了访问每个节点而引用若干不同的内存页面。

如果将节点和分支分配在单个页面上,那么节点就不一定会互相靠在一起。在最坏的情况下,每个内存页面上可能只有一个节点,而其余的每个页面则由分支占用。在这种情况下,遍历链接表将可能导致每个节点的页面出错,从而使进程运行得极慢。

18.2.4 减少线程同步的开销

正如下面就要介绍的那样,按照默认设置,堆栈是顺序运行的,这样,如果多个线程试图同时访问堆栈,就不会使数据受到破坏。但是,堆栈函数必须执行额外的代码,以保证堆栈对线程的安全性。如果要进行大量的堆栈分配操作,那么执行这些额外的代码会增加很大的负担,从而降低你的应用程序的运行性能。当你创建一个新堆栈时,可以告诉系统,只有一个线程将访问该堆栈,因此额外的代码将不执行。但是要注意,现在你要负责保证堆栈对线程的安全性。系统将不对此负责。

18.2.5 迅速释放堆栈

最后要说明的是,将专用堆栈用于某些数据结构后,就可以释放整个堆栈,而不必显式释放堆栈中的每个内存块。例如,当Windows Explorer遍历硬盘驱动器的目录层次结构时,它必须在内存中建立一个树状结构。如果你告诉 Windows Explorer刷新它的显示器,它只需要撤消包含这个树状结构的堆栈并且重新运行即可(当然,假定它将专用堆栈用于存放目录树信息)。对于许多应用程序来说,这是非常方便的,并且它们也能更快地运行。

之后的内容就是介绍一些函数,这里我就把函数写出来,具体使用细节可以查看文档。

1.创建辅助堆栈
HANDLE HeapCreate(
DWORD fdwOptions,
SIZE_T dwInitialSize,
SIZE_T dwMaximumSize);
2.从堆栈中分配内存块
PVOID HeapAlloc(
HANDLE hHeap,
DWORD fdwFlags,
SIZE_T dwButes);
3.改变内存块大小
PVOID HeapReAlloc(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem,
SIZE_T dwBytes);
4.了解内存块大小
SIZE_T HeapSize(
HANDLE hHeap,
DWORD fdwFlags,
LPCVOID pvMem);
5.释放内存块
BOOL HeapFree(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem);
6.撤销堆栈
BOOL HeapDestroy(HANDLE hHeap);
7.获取现有堆栈信息
DWPRD GetProcessHeaps(
DWORD dwNumHeaps,
PNANDLE pHeaps);
8.验证堆栈完整性
BOOL HeapVa;odate(
HANDLE hHeap,
DWORD fdwFlags,
LPCVOID pvMem);
9.合并地址中的空闲内存块回收不包含已经分配的地址内存块的存储器页面
UINT HeapCompact(
HANDLE hHeap,
DWORD fdwFlags); 10.堆栈上锁
BOOL HeapLock(HANDLE hHeap);
BOOL HeapUnLock(HANDLE hHeap);
11.遍历堆栈内容
BOOL HeapWalk(
HANDLE hHeap,
PPROCESS_HEAP_ENTRY pHeapEntry);

Windows核心编程 第十八章 堆栈的更多相关文章

  1. 【windows核心编程】 第八章 用户模式下的线程同步

    Windows核心编程 第八章 用户模式下的线程同步 1. 线程之间通信发生在以下两种情况: ①    需要让多个线程同时访问一个共享资源,同时不能破坏资源的完整性 ②    一个线程需要通知其他线程 ...

  2. Windows核心编程 第十九章 DLL基础

    第1 9章 D L L基础 这章是介绍基本dll,我就记录一些简单应用,dll的坑点以及扩展后面两章会说,到时候在总结. 自从M i c r o s o f t公司推出第一个版本的Wi n d o w ...

  3. Windows核心编程 第十五章 在应用程序中使用虚拟内存

    第1 5章 在应用程序中使用虚拟内存 Wi n d o w s提供了3种进行内存管理的方法,它们是: • 虚拟内存,最适合用来管理大型对象或结构数组. • 内存映射文件,最适合用来管理大型数据流(通常 ...

  4. 《Windows核心编程》第八章——用户模式下的线程同步

    下面起了两个线程,每个对一个全局变量加500次,不假思索进行回答,会认为最后这个全局变量的值会是1000,然而事实并不是这样: #include<iostream> #include &l ...

  5. Windows核心编程 第十二章 纤程

    第1 2章 纤 程 M i c r o s o f t公司给Wi n d o w s添加了一种纤程,以便能够非常容易地将现有的 U N I X服务器应用程序移植到Wi n d o w s中.U N I ...

  6. Windows核心编程 第十四章 虚拟内存

    第1 4章 虚 拟 内 存 <这一章没啥,是说的几个内存相关的函数 > 14.1 系统信息 许多操作系统的值是根据主机而定的,比如页面的大小,分配粒度的大小等.这些值决不应该用硬编码的形式 ...

  7. 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。

    windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...

  8. windows核心编程---第八章 使用内核对象进行线程同步

    使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...

  9. 《windows核心编程系列》十八谈谈windows钩子

    windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功 ...

随机推荐

  1. C# 应用 - 使用 WepApp 接受 Http 请求

    库类: Owin.dll Owin.IAppBuilder Microsoft.Owin.dll Microsoft.Owin.OwinContext Microsoft.Owin.Hosting.d ...

  2. 【译】Rust宏:教程与示例(二)

    原文标题:Macros in Rust: A tutorial with examples 原文链接:https://blog.logrocket.com/macros-in-rust-a-tutor ...

  3. Java 并发编程之 Condition 接口

    本文部分摘自<Java 并发编程的艺术> 概述 任意一个 Java 对象,都拥有一个监视器方法,主要包括 wait().wait(long timeout).notify() 以及 not ...

  4. python基础学习之集合set

    .集合:set 特点:无序,不可重复(自动去重),可更改,可以与元组.列表互相转换 格式:s = {'x','y','z'} 转换:(转回用set) s = {'x','y','z'}        ...

  5. 如何强制删除一个apk

    有些apk安装完后无法卸载,现在收集了一些方法,以及个人的一些手段. 1. 假设该app名为ketech,安装包名为ketech.apk. 2. 查看/data/app里面是否有名称包含ketech的 ...

  6. React实用技巧

    取消请求 React 中当前正在发出请求的组件从页面上卸载了,理想情况下这个请求也应该取消掉,那么如何把请求的取消和页面的卸载关联在一起呢? 这里要考虑利用 useEffect 传入函数的返回值: u ...

  7. python 实现输出一个等腰三角形

    这个问题实际上是一个数学问题,我们主要找出每行的规律就可以根据规律来书写代码 """ 2 代码实现输出一个等腰三角形,实际上就是一个等差数列求各项的一个数学> 问题 ...

  8. Html5分页显示Table

    Html: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <met ...

  9. Spring Boot 自动装配原理

    Spring Boot 自动装配原理 Spring Boot 在启动之前还有一系列的准备工作,比如:推断 web 应用类型,设置初始化器,设置监听器,启动各种监听器,准备环境,创建 applicati ...

  10. 利用浏览器favicon的缓存机制(F-Cache)生成客户端浏览器唯一指纹

    利用浏览器favicon的缓存机制(F-Cache)生成客户端浏览器唯一指纹 首先介绍下: 这个技术出自 UIC论文:https://www.cs.uic.edu/~polakis/papers/so ...