[笔记]Windows核心编程《十八》堆栈(二)

简介: [笔记]Windows核心编程《十八》堆栈(二)

3.1 从堆栈中分配内存块

若要从堆栈中分配内存块,只需要调用HeapAlloc函数:

PVOID HeapAlloc(
  HANDLE hHeap,
  DWORD fdwF1ags,
  SIZE_T dwBytes 
);

hHeap:用于标识分配的内存块来自的堆栈的句柄。

dwByte:参数用于设定从堆栈中分配的内存块的字节数。

fdwFlags:用于设定影响分配的各个标志。目前支持的标志只有3个,即 HEAP_ZERO_MEMORY、HEAP_GENERATE_EXCEPTIONS和HEAP _NO_SERIALIZE。

  1. HEAP_ZERO_MEMORY标志的作用应该是非常清楚的。该标志使得 HeapAlloc在返回前用 0来填写内存块的内容。
  2. HEAP_GENERATE_EXCEPTIONS标志 用于在堆栈中没有足够的内存来满足需求时使 HeapAlloc函数引发一个软件异常条件。 当用HeapCreate函数创建堆栈时,可以设定HEAP_GENERATE_EXCEPTIONS标志,它告诉堆栈,当不能分配内存块时,就应该引发一个异常条件。
    如果在调用HeapCreate函数时设定了这个标志,那么当调用HeapAlloc函数时,就不需要设定该标志。另外,你可能想要不使用该标志来创建堆栈。在这种情况下,为HeapAlloc函数设定该标志只会影响对HeapAlloc函数的一次调用,并不是每次调用都会受到影响。
    如果HeapAlloc运行失败,引发一个异常条件,那么这个异常条件将是表 18-1中的两个异
    常条件之一。

    如果内存块已经成功地分配, HeapAlloc返回内存块的地址。如果内存不能分配并且没有
    设定HEAP_GENERATE_EXCEPTIONS标志,那么HeapAlloc函数返回NULL。
  3. HEAP _NO_SERIALIZE标志 可以用来强制对HeapAlloc函数的调用与访问同一个
    堆栈的其他线程不按照顺序进行。在使用这个标志时应该格外小心,因为如果其他线程在同一
    时间使用该堆栈,那么堆栈就会被破坏。当从你的进程的默认堆栈中分配内存块时,决不要使
    用这个标志,因为数据可能被破坏,你的进程中的其他线程可能在同一时间访问默认堆栈。

Windows 98 如果调用HeapAlloc函数并且要求分配大于256 MB的内存块,Wi n d o w s

98 就将它看成是一个错误,函数的调用将失败。

注意:在这种情况下,该函数总是返回NULL,并且不会引发异常条件,即使你在创建堆栈或者试图分配内存块时使用HEAP_GENERATE_EXCEPTIONS标志,也不会引发异常条件。

注意:当你分配较大的内存块(大约1 MB或者更大)时,最好使用VirtualAlloc函数,应该避免使用堆栈函数。

3.2 改变内存块的大小

场景:

  • 有些应用程序开始时分配的内存块比较大,然后,当所有数据放入内存块后,再缩小内存块的大小。
  • 有些应用程序开始时分配的内存块比较小,后来需要将更多的数据拷贝到内存块中去时,再设法扩大它的大小。

如果要改变内存块的大小,可以调用H e a p R e A l l o c函数:

PVOID HeapReAlloc(
  HANDLE hHeap,
  DWORD fdwF1ags,
  PVOID pvMem,
  SIZE_T dwBytes 
);

hHeap参数:用于指明包含你要改变其大小的内存块的堆栈。

fdwFlags参数:用于设定改变内存块大小时H e a p R e A l l o c函数应该使用的标志。可以使用的标志只有下面4个,即

  • HEAP_GENERATE_EXC EPTIONS
  • HEAP_NO_SERIALIZE
  • HEAP_ZERO_MEMORY
  • HEAP_REALLOC_IN_PLACE_ONLY。

前面两个标志在用于HeapAlloc时,其作用相同。HEAP_ZERO_MEMORY标志只有在你扩大内存块时才使用。在这种情况下,内存块中增加的字节将被置 0。如果内存块已经被缩小,那么该标志不起作用。HEAP_REALLOC_ IN_PLACE_ONLY标志告诉HeapReAlloc函数,它不能移动堆栈中的内存块。如果内存块在增大,HeapReAlloc函数可能试图移动内存块。如果HeapReAlloc能够扩大内存块而不移动它,那么它将会这样做并且返回内存块的原始地址。另外,如果 HeapReAlloc必须移动内存块的内容,则返回新的较大内存块的地址。如果内存块被缩小, HeapReAlloc将返回内存块的原始地址。如果内存块是链接表或二进制树的组成部分,那么可以设定HEAP_REALLOC_IN_PLACE_ONLY标志。在这种情况下,链接表或二进制树中的其他节点可能拥有该节点的指针,改变堆栈中的节点位置会破坏链接表的完整性。

pvMem:用于设定你要改变其大小的内存块的地址

dwBytes:内存块的新的大小(以字节为计量单位)。

3.3 了解内存块的大小

当内存块分配后,可以调用HeapS i z e函数来检索内存块的实际大小:

SIZE_T Heapsize(
  HANDLE hHeap,
  DWORD fdwFlags,
  LPCV01D pvMem
);

hHeap:用于标识堆栈,参数pvMem用于指明内存块的地址。参数 fdwFlags既可以是0,

也可以是HEAP_NO_SERIALIZE。

3.4 释放内存块

当不再需要内存块时,可以调用H e a p F r e e函数将它释放:

B00L HeapFree(
  HANDLE hHeap,
  DWORD fdwF1ags,
  PVOID pvMem
);

HeapFree函数用于释放内存块,如果它运行成功,便返回TRUE。参数fdwF lags既可以是0,也可以是HEAP_NO_SERIALIZE。调用这个函数可使堆栈管理器收回某些物理存储器,但是这没有保证。

3.5 撤消堆栈

如果应用程序不再需要它创建的堆栈,可以通过调用 HeapDestroy 函数将它撤消:

BO0L HeapDestroy (HANDLE hHeap);

调用HeapDestroy函数可以释放堆栈中包含的所有内存块,也可以将堆栈占用的物理存储器和保留的地址空间区域重新返回给系统。如果该函数运行成功,HeapDestroy返回T R U E。如果在进程终止运行之前没有显式撤消堆栈,那么系统将为你将它撤消。但是,只有当进程终止运行时,堆栈才能被撤消。如果线程创建了一个堆栈,当线程终止运行时,该堆栈将不会被撤消。在进程完全终止运行之前,系统不允许进程的默认堆栈被撤消。如果将进程的默认堆栈的句柄传递给HeapDestroy 函数,系统将忽略对该函数的调用。

3.6 用C++程序来使用堆栈

cSomeC1ass* pSomeC1ass = new CSomeC1ass;
delete pSomeC1ass;

重载new和delete操作符,就能够很容易地利用堆栈函数。

CSomeClass类定义为如下的形式:

c1ass CSomeClass {
private:
  static HANDLE s_hHeap;
  static UINT s_uNumAllocsInHeap;
  //other private data and member functions
pub1ic:
  void* operator new(size_t size);
  void  operator de1ete(void* p);
  //other public data and . member functions
}
HANDLE CSomeClass::s_.hHeap = NULL;
UINT CSomeC1ass::s__uNumA11ocs InHeap = 0;
void* CSomeClass::operator new ( size_t size) 
{
  if (s_hHeap == NULL) 
  {
    //Heap does not exist; create it.
    s_hHeap = HeapCreate( HEAP_NO_SERIALIZE,0,0);
    if( s_hHeap == NULL)
      return (NULL);
  }
  //The heap exists for CSomeC1ass objects.
  void* p = HeapA11oc( shHeap. 0,size);
  if (p != NULL)
  {
    //Memory was a1located successfu1ly; incrementll the count of   CSomeC1ass objects in the heap.
    s_uNumA11ocsInHeap++;
  }
  //Return the address of the a1located cSomeC1ass object.
  return(p);
}
void CSomeClass::operator delete (void* p)
{
  if ( HeapFree( s_hHeap. 0. p)) 
  {
    //Object was de1eted successfu11y.
    s_uNumA11ocsInHeap--;
  }
  if (s_uNumA11ocsInHeap ==0 )
  {
    //If there are no more objects in the heap.ll destroy the heap.
    if (HeapDestroy ( s_hHeap)) 
    {
      //Set the heap handle to NULL so that the new operatorll wi11 know to create a new heap if a new csomeC1assll object is created.
      s_hHeap = NULL;
    }
  }
}

CSomeClass类的所有实例都在相同的堆栈中分配。

s_hHeap:包含分配CSomeClass对象时所在堆栈的句柄。s_uNumAllocsInHeap:只是一个计数器,用于计算堆栈中已经分配了多少个CSomeClass对象。

四、其他堆栈函数

获取现有堆栈的句柄

由于进程的地址空间中可以存在多个堆栈,因此可以使用 GetProcessHeaps函数来获取现有堆栈的句柄:

DWORD GetProcessHeaps(
  DWORD dwNumHeaps,
  PHANDLE pHeaps 
);

若要调用GetProcessHeaps函数,必须首先分配一个HANDLE数组,然后调用下面的函数:

HANDLE hHeaps[25];
DWORD dwHeaps = GetProcessHeaps(25,hHeaps);
if (dwHeaps > 25)
{
  //More heaps are in this process than we expected.
}e1se {
  //hHeaps[0] through hHeap[dwHeaps - 1]
  //identify the existing heaps.
}

验证堆栈的完整性

HeapValidate函数用于验证堆栈的完整性

B00L HeapValidate(
  HANDLE hHeap,
  DWORD fdwF1ags,
  LPCVOID pvMem
);

合并地址中的空闲内存块

合并地址中的空闲内存块并收回不包含已经分配的地址内存块的存储器页面 函数:

UINT HeapCompact(
  HANDLE hHeap,
  DWORD fdwF1ags 
);

通常情况下,可以为参数f d w F l a g s传递0,但是也可以传递HEAP_NO_SERIALIZE。

用于堆栈线程同步

BOOL HeapLock(HANDLE hHeap);
B00L HeapUn1ock( HANDLE hHeap);

当调用HeapLock函数时,调用线程将成为特定堆栈的所有者。

如果其他任何线程调用堆栈函数(设定相同的堆栈句柄),系统将暂停调用线程的运行,并且在堆栈被HeapUn1ock函数解锁之前不允许它醒来。

遍历堆栈的内容

B0OL Heapwalk(
  HANDLE hHeap,
  PPROCESS_HEAP_ENTRY pHeapEntry 
);

该函数只用于调试目的。它使你能够遍历堆栈的内容。可以多次调用该函数。

每次调用该函数时将传递必须分配和初始化的PROCESS_HEAP_ENTRY结构的地址:

typedef struct _PROCESS_HEAP_ENTRY
{
  PVOID lpData;
  DWORD cbData;
  BYTE cbOverhead;
  BYTE iRegionIndex;
  WORD wF1ags;
  union{
    struct {
      HANDLE hMem;
      DWORD dwReserved[ 3 ];
    }B1ock;
    struct {
        DMORD dwCommittedsize;
        DwORD dwUnCommittedSize;
        LPVOID lpFirstB1ock;
        LPVOID 1pLastB1ock;
    }Resion;  
  };
   }
}PROCESS_HEAP_ENTRY,*LPPROCESS_HEAP_ENTRY,*PPROCESS_HEAP_ENTRY;

当开始枚举堆栈中的内存块时,必须将成员 lpData设置为NULL。这将告诉Heapwalk对该结构中的成员进行初始化。当成功地调用 Heapwalk后,可以查看该结构的成员。

若要进入堆栈的下一个内存块,只需要再次调用Heapwalk,传递相同的堆栈句柄和在上次调用该函数时传递的PROCESS_HEAP_ENTRY结构的地址。当Heapwalk返回FALSE时,堆栈中就没有更多的内存块了。关于该结构中的成员的说明,请参见 Platform SDK文档。

在循环调用Heapwalk的时候,必须使用HeapLock和HeapLock函数,这样,当遍历堆栈时,其他线程将无法分配和释放堆栈中的内存块。

总结

1.创建堆栈HeapCreate和分配内存HeapAlloc

HeapCreate:创建堆栈。

HeapAlloc:创建给堆栈分配内存,返回内存地址。

2.堆栈,堆和栈的区别

计算机世界里的“堆栈”你真的懂吗?

相关文章
|
1月前
|
监控 Ubuntu Linux
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
这篇文章介绍了如何在Ubuntu和Windows系统中通过设置相同的时区并使用ntp服务来解决时间同步问题。
64 4
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
|
2月前
|
网络协议 API Windows
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
|
2月前
|
Windows
[原创]用MASM32编程获取windows类型
[原创]用MASM32编程获取windows类型
|
2月前
|
JavaScript 前端开发 API
MASM32编程通过WMI获取Windows计划任务
MASM32编程通过WMI获取Windows计划任务
|
2月前
|
API Windows
MASM32编程获取Windows当前桌面主题名
MASM32编程获取Windows当前桌面主题名
|
3月前
|
编译器 开发工具 C语言
解锁QtCreator跨界神技!Windows下轻松驾驭OpenCV动态库,让你的跨平台开发如虎添翼,秒变视觉编程大师!
【8月更文挑战第4天】QtCreator是一款强大的跨平台IDE,便于创建多平台应用。本教程教你如何在Windows环境下集成OpenCV库至Qt项目。首先,下载匹配MinGW的OpenCV预编译版并解压。接着,在QtCreator中新建或打开项目,并在.pro文件中添加OpenCV的头文件和库文件路径。确保编译器设置正确。随后编写测试代码,例如加载和显示图片,并进行编译运行。完成这些步骤后,你就能在QtCreator中利用OpenCV进行图像处理开发了。
220 6
|
3月前
|
数据库 Windows
超详细步骤解析:从零开始,手把手教你使用 Visual Studio 打造你的第一个 Windows Forms 应用程序,菜鸟也能轻松上手的编程入门指南来了!
【8月更文挑战第31天】创建你的第一个Windows Forms (WinForms) 应用程序是一个激动人心的过程,尤其适合编程新手。本指南将带你逐步完成一个简单WinForms 应用的开发。首先,在Visual Studio 中创建一个“Windows Forms App (.NET)”项目,命名为“我的第一个WinForms 应用”。接着,在空白窗体中添加一个按钮和一个标签控件,并设置按钮文本为“点击我”。然后,为按钮添加点击事件处理程序`button1_Click`,实现点击按钮后更新标签文本为“你好,你刚刚点击了按钮!”。
261 0
|
4月前
|
Linux Apache C++
FFmpeg开发笔记(三十五)Windows环境给FFmpeg集成libsrt
该文介绍了如何在Windows环境下为FFmpeg集成SRT协议支持库libsrt。首先,需要安装Perl和Nasm,然后编译OpenSSL。接着,下载libsrt源码并使用CMake配置,生成VS工程并编译生成srt.dll和srt.lib。最后,将编译出的库文件和头文件按照特定目录结构放置,并更新环境变量,重新配置启用libsrt的FFmpeg并进行编译安装。该过程有助于优化直播推流的性能,减少卡顿问题。
123 2
FFmpeg开发笔记(三十五)Windows环境给FFmpeg集成libsrt
|
4月前
|
存储 安全 数据安全/隐私保护
Windows 32 汇编笔记(一):基础知识
Windows 32 汇编笔记(一):基础知识
|
3月前
|
存储 编译器 Linux
Windows 32 汇编笔记(二):使用 MASM
Windows 32 汇编笔记(二):使用 MASM

热门文章

最新文章

下一篇
无影云桌面