说说VirtualAlloc的第三个参数

说说VirtualAlloc的第三个参数

VirtualAlloc的第三个参数详解

Windows的VirtualAlloc函数是内存管理的核心API,它负责在进程的虚拟地址空间中分配内存。这个函数有四个主要参数,其中第三个参数常常被简单带过,但它实际上包含了内存分配行为的关键控制信息。

基本语法回顾

先看VirtualAlloc的函数原型:

c 复制代码
LPVOID VirtualAlloc(
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flAllocationType,
  DWORD  flProtect
);

第三个参数flAllocationType是一个DWORD类型的标志组合,它告诉系统我们要如何分配内存。这个参数不是可选的,而且它的取值直接影响内存的使用效率和功能。

主要分配类型标志

MEM_COMMIT - 提交物理存储

这是最常用的标志。当指定MEM_COMMIT时,系统会为内存区域分配物理存储(RAM或页面文件)。简单说,就是真的给你分配了可用的内存空间。

c 复制代码
// 提交4KB内存
LPVOID pMem = VirtualAlloc(NULL, 4096, MEM_COMMIT, PAGE_READWRITE);

提交内存后,就可以正常读写这块内存了。如果没有提交,访问这块内存会导致访问违规异常。

提交是必须的步骤,不提交的内存只是"保留"的地址空间,不能实际使用。但提交不一定要在分配时立即进行,可以先保留地址空间,需要时再提交。

MEM_RESERVE - 保留地址空间

保留地址空间的意思是,告诉系统:"这块虚拟地址空间我先占了,但先不给我实际的物理内存"。系统会记录这块地址空间已被预留,其他分配不会占用这块区域。

c 复制代码
// 保留1GB的地址空间
LPVOID pReserved = VirtualAlloc(NULL, 1024*1024*1024, MEM_RESERVE, PAGE_READWRITE);

保留的地址空间不能直接访问,尝试读写会导致异常。它的主要用途是预留大块的连续地址空间,供以后逐步使用。

大型应用程序如数据库、虚拟机监控程序经常使用这个技术。先保留足够大的地址空间,然后按需提交实际需要的部分。这样可以避免地址空间碎片化。

MEM_COMMIT | MEM_RESERVE - 组合使用

最常见的用法是两者组合:

c 复制代码
// 同时保留和提交内存
LPVOID pMem = VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

这相当于"我要这块地址空间,而且现在就要用"。对大多数一次性分配的需求,这是标准做法。

其他重要标志

MEM_LARGE_PAGES - 大页面支持

Windows支持大于标准4KB的页面大小,通常是2MB或1GB(取决于CPU架构和配置)。大页面可以提高TLB命中率,提升内存访问性能。

c 复制代码
// 分配大页面内存,size必须是GetLargePageMinimum()的倍数
SIZE_T largePageSize = GetLargePageMinimum();
LPVOID pLargePage = VirtualAlloc(NULL, largePageSize, 
                                  MEM_COMMIT | MEM_LARGE_PAGES, 
                                  PAGE_READWRITE);

使用大页面有几个限制:需要SeLockMemoryPrivilege权限,大小必须是大页面大小的整数倍,而且不能与某些其他标志一起使用。

大页面内存在性能关键的应用中很有用,比如高性能计算、数据库缓冲池等。

MEM_PHYSICAL - 分配物理内存

这个标志用于分配物理内存,不通过页面文件。分配的内存不会因内存压力而被交换到磁盘。

c 复制代码
// 分配物理内存
LPVOID pPhysical = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_PHYSICAL, PAGE_READWRITE);

这需要SeLockMemoryPrivilege权限。主要用于需要保证内存驻留的实时应用,或者避免内存交换对性能有严重影响的场景。

MEM_TOP_DOWN - 从高地址开始分配

默认情况下,内存从低地址向高地址分配。指定MEM_TOP_DOWN会让系统从用户模式地址空间的高地址端开始分配。

c 复制代码
// 从高地址开始分配
LPVOID pTopDown = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE);

这个标志在调试和某些特殊场景中有用。比如,有些漏洞利用依赖特定地址的内存布局,从高地址分配可以增加利用难度。

MEM_WRITE_WATCH - 启用写入监视

这个标志比较特殊,它让系统跟踪对内存页的写入操作。

c 复制代码
// 分配支持写入监视的内存
LPVOID pWriteWatch = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE);

分配后,可以使用GetWriteWatch函数获取哪些页面被写入过。这在垃圾回收器的实现、内存变化检测等场景中有用。

标志组合的注意事项

不是所有标志都能任意组合。有些组合是有意义的,有些是冲突的。

有效的组合:

  • MEM_RESERVE | MEM_COMMIT(最常见)
  • MEM_COMMIT | MEM_LARGE_PAGES
  • MEM_COMMIT | MEM_PHYSICAL
  • MEM_COMMIT | MEM_TOP_DOWN

无效的组合:

  • MEM_RESERVE单独与MEM_LARGE_PAGES组合(大页面必须先保留后提交)
  • 同时使用MEM_LARGE_PAGES和MEM_WRITE_WATCH

系统会检查标志的合理性。如果提供了无效组合,VirtualAlloc会失败,GetLastError返回ERROR_INVALID_PARAMETER。

调试相关

在调试时,分配类型标志会影响内存的可观察性:

  • MEM_COMMIT的内存可以在调试器中看到内容
  • MEM_RESERVE的内存显示为"??"(无效内存)
  • 使用MEM_WRITE_WATCH可以帮助跟踪内存修改

Windbg的!address命令会显示内存区域的分配类型,对分析内存问题很有帮助。

错误处理

VirtualAlloc失败时,GetLastError可能返回:

  • ERROR_INVALID_PARAMETER:无效的标志组合
  • ERROR_NOT_ENOUGH_MEMORY:物理内存不足
  • ERROR_COMMITMENT_LIMIT:超出提交限制
  • ERROR_ACCESS_DENIED:权限不足(如大页面需要特权)

正确的错误处理应该检查这些错误码,给用户明确的提示。

相关推荐
John_ToDebug5 小时前
隐于无形,触手可及:Chrome 互动滚动条的六个设计密码
chrome·windows·ui
思茂信息6 小时前
CST软件如何进行参数化扫描?
运维·开发语言·javascript·windows·ecmascript·软件工程·软件需求
开发者联盟league7 小时前
在windows上安装和运行rocketmq
windows·rocketmq
非凡ghost10 小时前
可拓浏览器:给手机浏览器装上“外挂“!2W+拓展+AI搜索,玩出无限可能!
windows·智能手机·音视频·firefox
小神.Chen10 小时前
如何删除远程桌面的连接记录,一键清理mstsc远程桌面连接的记录
windows
John_ToDebug10 小时前
WebHostView 与 TabStrip 交互机制深度解析
c++·chrome·windows
L16247611 小时前
Win11 共享→Windows Server 访问故障总结(极简可复用)
开发语言·windows·php
love530love11 小时前
ComfyUI MediaPipe 终极填坑:解决 incompatible function arguments 报错,基于代理模式的猴子补丁升级版
人工智能·windows·comfyui·mediapipe·猴子补丁·monkey patch·python 3.12
今夕资源网12 小时前
Windows Terminal更舒适的命令行环境 仅11MB 支持并行运行WSLLinux子系统 github开源项目
windows·github·命令行·cmd·terminal
java_logo13 小时前
SiYuan 思源笔记 Docker 部署终极指南:Windows+Linux 双平台
windows·笔记·docker·思源笔记·思源笔记部署·docker部署思源笔记·思源笔记文档