[笔记]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月前
|
缓存 网络协议 数据安全/隐私保护
[运维笔记] - (命令).Windows server常用网络相关命令总结
[运维笔记] - (命令).Windows server常用网络相关命令总结
232 0
|
21天前
|
编解码 5G Linux
FFmpeg开发笔记(二十一)Windows环境给FFmpeg集成AVS3解码器
AVS3是中国首个8K及5G视频编码标准,相比AVS2和HEVC性能提升约30%。解码器libuavs3d支持8K/60P视频实时解码,兼容多种平台。《FFmpeg开发实战》书中介绍了在Windows环境下如何集成libuavs3d到FFmpeg。集成步骤包括下载源码、使用Visual Studio 2022编译、调整配置、安装库文件和头文件,以及重新配置和编译FFmpeg以启用libuavs3d。
36 0
FFmpeg开发笔记(二十一)Windows环境给FFmpeg集成AVS3解码器
|
1月前
|
算法 Linux Windows
FFmpeg开发笔记(十七)Windows环境给FFmpeg集成字幕库libass
在Windows环境下为FFmpeg集成字幕渲染库libass涉及多个步骤,包括安装freetype、libxml2、gperf、fontconfig、fribidi、harfbuzz和libass。每个库的安装都需要下载源码、配置、编译和安装,并更新PKG_CONFIG_PATH环境变量。最后,重新配置并编译FFmpeg以启用libass及相关依赖。完成上述步骤后,通过`ffmpeg -version`确认libass已成功集成。
41 1
FFmpeg开发笔记(十七)Windows环境给FFmpeg集成字幕库libass
|
1月前
|
编解码 Linux Windows
FFmpeg开发笔记(十三)Windows环境给FFmpeg集成libopus和libvpx
本文档介绍了在Windows环境下如何为FFmpeg集成libopus和libvpx库。首先,详细阐述了安装libopus的步骤,包括下载源码、配置、编译和安装,并更新环境变量。接着,同样详细说明了libvpx的安装过程,注意需启用--enable-pic选项以避免编译错误。最后,介绍了重新配置并编译FFmpeg以启用这两个库,通过`ffmpeg -version`检查是否成功集成。整个过程参照了《FFmpeg开发实战:从零基础到短视频上线》一书的相关章节。
51 0
FFmpeg开发笔记(十三)Windows环境给FFmpeg集成libopus和libvpx
|
1月前
|
Rust 前端开发 Windows
blog-engine-06-pelican 静态网站生成 windows11 安装实战笔记
这篇内容是一个关于在Windows 11上安装和使用静态网站生成器的教程,主要包括对多个博客引擎(如Jekyll、Hugo、Hexo等)的简介和对比,以及详细步骤教你如何在Windows环境下安装Python、Pelican、Rust和Cargo。作者首先介绍了Python和Pelican的安装,然后在遇到依赖问题时,引导读者安装Rust和Cargo来解决。最后,通过`pelican-quickstart`创建项目,编写Markdown文章并生成、预览站点。
|
1月前
|
编解码 Linux Windows
FFmpeg开发笔记(十一)Windows环境给FFmpeg集成vorbis和amr
在Windows环境下,为FFmpeg集成音频编解码库,包括libogg、libvorbis和opencore-amr,涉及下载源码、配置、编译和安装步骤。首先,安装libogg,通过配置、make和make install命令完成,并更新PKG_CONFIG_PATH。接着,安装libvorbis,同样配置、编译和安装,并修改pkgconfig文件。之后,安装opencore-amr。最后,重新配置并编译FFmpeg,启用ogg和amr支持,通过ffmpeg -version检查是否成功。整个过程需确保环境变量设置正确,并根据路径添加相应库。
41 1
FFmpeg开发笔记(十一)Windows环境给FFmpeg集成vorbis和amr
|
1月前
|
API C++ Windows
windows编程入门_链接错误的配置
windows编程入门_链接错误的配置
22 0
|
1月前
|
机器学习/深度学习 安全 数据安全/隐私保护
Windows系统安装Jupyter Notebook并实现公网访问内网笔记服务
Windows系统安装Jupyter Notebook并实现公网访问内网笔记服务
|
1月前
|
Linux 编译器 C语言
FFmpeg开发笔记(二)搭建Windows系统的开发环境
在Windows上学习FFmpeg通常较困难,但通过安装预编译的FFmpeg开发包可以简化流程。首先需要安装MSYS2来模拟Linux环境。下载并执行MSYS2安装包,然后修改msys2_shell.cmd以继承Windows的Path变量。使用pacman安装必要的编译工具。接着,下载预编译的FFmpeg Windows包,解压并配置系统Path。最后,在MSYS2环境中运行`ffmpeg -version`确认安装成功。欲深入学习FFmpeg开发,推荐阅读《FFmpeg开发实战:从零基础到短视频上线》。
48 4
FFmpeg开发笔记(二)搭建Windows系统的开发环境
|
1月前
|
Windows
火山中文编程 -- 第一个windows程序
火山中文编程 -- 第一个windows程序
17 0