发布于2026-07-23 阅读(0)
扫一扫,手机访问
先说两个基础概念,搞懂了它们,后面的东西才能串起来。
这没什么好说的,就是计算机硬件里实实在在的RAM,比如你插的那根DDR5内存条。它直接通过总线跟CPU打交道,专门用来临时存着正在运行的程序和数据。
这个就有点意思了。它是操作系统凭空“编”出来的一层抽象内存,把物理内存和磁盘空间(比如页面文件或者交换分区)搅在一起,给每个程序都提供一段连续、独立的内存空间。说白了,你写程序的时候,根本不用管数据到底在内存条上,还是在硬盘的某个角落里,只需要跟虚拟内存地址打交道就行。
用户只需要与虚拟内存地址打交道,而无需关心数据到底分配在哪里

眼见为实

在Windows系统里,一切内存操作都以4K为最小单位,这个单位就叫物理页。操作系统分配内存时,永远是4K的整数倍。举个例子,你申请1K,它给你分配4K;你申请5K,它就给你分配8K。就是这么粗暴,但高效。
眼见为实
void page4k() {for (int i = 0; i < 200; i++) {//1k 的占用LPVOID ptr = VirtualAlloc(NULL, 1024 * 1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);printf("i=%d, 1k, address:%#0.8x \n", i + 1, ptr);}for (int i = 200; i < 400; i++) {//5k 的占用LPVOID ptr = VirtualAlloc(NULL, 1024 * 5, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);printf("i=%d, 5k, address:%#0.8x \n", i + 1, ptr);}getchar();}
申请1k分配4k

申请5k分配8k

虚拟地址和物理地址之间怎么搭上线?Windows用了二叉树结构(5层)来实现这种高效映射。这层映射关系,是整个内存管理的核心。
举个例子,假设有个32bit的内存地址:0x77b01a42,它的二进制是:01110,11110,11000,00001,101001000010。这串数字怎么用呢?


前20位用来构建页表树,实现物理页的高效映射后12位映射物理页内的偏移量操作系统以4K为一个单位对内存进行分组。4G内存有多少个物理页?算一下:1024*1024*1024*4/(4/1024) = 1048576个。上百万个物理页,如果没有这颗5层二叉树去索引,效率简直不敢想象。
眼见为实:以notepad为例
任务管理:
Windbg:

看到没?同样一个notepad,任务管理器显示占用44.6MB内存,而Windbg显示占用489.531MB内存。为什么差距这么大?原因在于显示逻辑不同:任务管理器显示的是Private WorkingSet,这是物理内存,也就是内存条上的内存;而Windbg显示的是Commit,指的是虚拟内存地址,这包括内存条上的内存,pagefile,image三种。
眼见为实:可视化观察 虚拟地址 => 物理地址
用Windbg进入内核态,这很重要。大家可以猜猜为什么非得进内核态。
随便找一个字符串的内存地址


眼见为实:空指针区与用户态区

Windows和Linux在默认情况下,都会开启ASLR(地址空间布局随机化)。如果想把这块研究透,需要关闭这个技术才能复现。ASLR是一种针对缓冲区溢出攻击等内存攻击技术而设计的安全特性。没有ASLR时,程序加载到内存中的位置是固定的,攻击者可以预测模块的加载地址,从而构造恶意代码。启用ASLR后,操作系统在每次启动程序时会随机化内存布局,包括可执行文件、动态链接库、堆、栈等的加载地址,大大增加攻击难度。
这两个概念,是理解虚拟内存分配的关键。
在虚拟地址上申请一段内存空间,此时操作系统会同步创建页表树,但并未映射到物理内存。这时候你要是对这块虚拟内存进行读写,等着你的就是异常。
给页表树调配真实的物理内存,只有这一步完成后,才能正常写入数据。
眼见为实: Reserved
void mem_reserved() {LPVOID ptr = VirtualAlloc(NULL, 4 * 1024, MEM_RESERVE, PAGE_READWRITE);*(int*)(ptr) = 10; //在首地址上写入内容。printf("num=%d", *(int*)ptr);}

眼见为实: Commit
void mem_commit() {LPVOID ptr = VirtualAlloc(NULL, 4 * 1024, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);*(int*)(ptr) = 10; //在首地址上写入内容。printf("num=%d", *(int*)ptr);}

NT堆是Windows NT内核引入的内存管理组件,主要负责进程内的堆内存分配与释放。在Windows系统里,进程使用NT堆来动态分配和管理内存,比如程序中的malloc()(C语言)、new(C++)等函数,底层通常就依赖NT堆机制。
前面说到,VirtualAlloc方法会一次性分配64K整数倍的内存段,内部对象按4K的内存页对齐。如果让应用程序直接操作VirtualAlloc,难免会造成大量内存浪费。为了提高内存性能与使用效率,Windows又提供了一层抽象,以提供更细颗粒度的内存管理,它就是NT堆。

从图中可以看出,用不用NT堆,完全取决于程序员自己。你可以绕过NT堆,直接使用VirtualAlloc来分配内存,只要你能接受内存浪费。
眼见为实:GC堆,底层使用VirtualAlloc分配内存
static void Main(string[] args) { var rand = new Random(); List list = new List(); for (int i = 0; i < 100000; i++) { var str = string.Join(",", Enumerable.Range(0, rand.Next(1, 1000))); list.Add(str); Console.WriteLine($"i={i},length={str.Length}"); } Console.ReadLine(); }
在bp KERNELBASE!VirtualAlloc 下断点


眼见为实:CRT堆/NT堆,底层使用VirtualAlloc分配内存
#include#include void crt_c() {for (int i = 0; i < 10000000; i++) {int* ptr = (int*)malloc(sizeof(int) * 1000);*(ptr) = 10;printf("第 %d 次分配 \n", i);}}
在 bp ntdll!NtAllocateVirtualMemory 下断点

售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8