1.windows内存地址空间
(1)地址空间
程序中可以寻址的最大范围,对于32操作系统,地址空间的范围为0-4G(2^32),地址空间越大,相对程序的编写就会容易。
(2)地址空间的划分
a.用户地址空间:0-2G(0-7FFFFFFF)
存放用户的程序和数据。用户空间的代码是不能访问内核空间的数据和代码。
空指针区(NULL区,0-64K)- 系统将地址小于64K的指针都认为是空指针。
用户区
64K禁入区(0x7FFEFFFF - 0x7FFFFFFF)
b.内核地址空间2G-4G
存放内核的代码和数据,例如系统驱动。内核空间代码是可以访问用户空间的。
2.windows内存
(1)区域
区域就是连续的一块内存。区域的大小一般是64K或64K的整数倍。每个区域都有自己的状态:
空闲:没有被使用
私有:被预定的区域
映像:存放代码
映射:存放数据
(2)物理内存(简单说就是插在主板上的内存条)
系统可以使用的实际内存。
CPU可以直接访问的只有内存,访问其他外围设备都是以内存做中转。
(3)硬盘交换文件(虚拟内存)
将硬盘文件虚拟成内存使用。(pagefile.sys文件)
CPU如果要访问虚拟内存数据,必须将虚拟内存数据放到物理内存。
那么我们申请的一段内存是物理内存还是硬盘交换文件呢?
以我们内存中数据的使用频率为标准,如果频率高,系统就会把数据放到物理内存;否则,就放到硬盘交换文件中。
(4)内存页
系统管理内存的最小单位。内存页大小为4k,每个内存页都有自己的权限。
(5)页目标
指针地址
31 22 21 12 11 0
|------------|-------------|------------|
10位 10位 12位
2^10 = 1024 1024 4k
页目(层号) 页表(页号) 页内偏移
(6)从内存获取数据的过程
a.根据地址在物理内存中查找相应的位置,如果找到物理内存,取回数据;如果未找到,执行b
b.根据地址去虚拟内存中查找相应的位置,如果找到,执行c;如果未找到,那么地址没有内存空间,返回错误
c.将该地址所在的内存页置换到物理内存中,同时将原物理内存数据存到虚拟内存中。
d.将物理内存中的数据返回给使用者。
(7)内存分配
虚拟内存分配:适合大内存分配,一般是1M以上的内存。
堆内存分配:适合小内存分配,一般是1M以下的内存。(malloc/new)
栈内存分配:适合小内存分配,一般是1M以下的内存。
3.虚拟内存分配
(1)虚拟内存分配
速度快,大内存效率高。将内存和地址分配分别执行,可以在需要的时候再提交内存。常用于大型电子表格等处理。
(2)虚拟内存使用
a.申请地址/提交内存
LPVOID VirtualAlloc( LPVOID lpAddress, //提交地址或NULL
SIZE_T dwSize, //分配的大小
DWORD flAllocationType, //分配方式
DWORD flProtect); //内存访问方式
分配成功,返回地址
分配方式:MEM_COMMIT - 提交内存,分配之后返回地址和内存空间
MEM_RESERVE - 保留地址,分配之后只返回地址,内存空间不生成。要使用内存必须再次提交。
b.使用
c.释放
BOOL VirtualFree( LPVOID lpAddress, //释放地址
SIZE_T dwSize, //释放的大小
DWORD dwFreeType); //释放的方式
释放方式:MEM_DECOMMIT - 只释放内存
MEM_RELEASE - 地址和内存都释放
补充:VOID GlobalMemoryStatus( LPMEMORYSTATUS lpBuffer ); //获得当前计算机物理和虚拟内存使用情况
相关代码:
#include "stdafx.h" #include "windows.h" #include "stdio.h" void Status() { MEMORYSTATUS ms = { 0 }; ms.dwLength = sizeof(ms); GlobalMemoryStatus(&ms); printf("Load:%d ", ms.dwMemoryLoad); printf("TotalPhys:%u ", ms.dwTotalPhys); printf("AvliaPhys:%u ", ms.dwAvailPhys); printf("TotalVirt:%u ", ms.dwTotalPageFile); printf("AvliaVirt:%u ", ms.dwAvailPageFile); printf("TotalAddr:%u ", ms.dwTotalVirtual); printf("AvliaAddr:%u ", ms.dwAvailVirtual); printf("******************* "); } void VirtualInfo() { /* MEM_COMMIT - 地址和内存都要 MEM_RESERVE - 只申请地址,不要内存 MEM_DECOMMINT - 只释放内存,不释放地址 MEM_RELEASE - 地址和内存都释放 */ Status(); //申请1G个地址和1G内存 char *pszText = (char*)VirtualAlloc(NULL, 1024*1024*1024, MEM_COMMIT, PAGE_READWRITE); Status(); VirtualFree(pszText, 0, MEM_RELEASE); Status(); } void VirtualCommit() { char *pszText = (char*)VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT, PAGE_READWRITE); strcpy_s(pszText, 13, "hello commit"); printf("%s ", pszText); VirtualFree(pszText, 0, MEM_RELEASE); } void VirtualReserve() { //分配1G地址 char *pszText = (char*)VirtualAlloc(NULL, 1024 * 1024 * 1024, MEM_RESERVE, PAGE_READWRITE); //提交内存,1个字节,但是提交是按内存页(4096字节)的整数倍倍提交 //char *pszText1 = (char*)VirtualAlloc(pszText, 1, MEM_COMMIT, PAGE_READWRITE); char *pszText1 = (char*)VirtualAlloc(pszText + 4096, 1, MEM_COMMIT, PAGE_READWRITE); strcpy_s(pszText1 + 4082, 14, "hello reserve"); printf("%s ", pszText1 + 4082); VirtualFree(pszText, 0, MEM_RELEASE); } int _tmain(int argc, _TCHAR* argv[]) { //VirtualInfo(); //VirtualCommit(); VirtualReserve(); return 0; }
运行结果:
4.堆内存Heap
(1)堆内存分配
适合小内存分配,一般小于1M的内存。
一般每个程序都有自己的堆,默认大小为1M,会根据使用情况进行调整。
(2)堆的使用
a.堆信息
GetProcessHeap - 获得程序的第一个堆
GetProcessHeaps - 获取程序中所有的堆
b.创建堆
HANDLE HeapCreate( DWORD flOptions, //创建选项
SIZE_T dwInitialSize, //初始化大小
SIZE_T dwMaximumSize); //最大值
成功返回堆句柄
创建选项:HEAP_GENERATE_EXCEPTIONS - 创建失败,抛出异常
HEAP_NO_SERIALIZE - 支持不连续存取
c.获取堆所维护内存的某部分地址(从堆中分配内存)
LPVOID HeapAlloc( HANDLE hHeap, //堆句柄
DWORD dwFlags, //分配方式
SIZE_T dwBytes); //分配大小
成功返回地址
d.使用内存
e.释放内存
BOOL HeapFree( HANDLE hHeap, //堆句柄
DWORD dwFlags, //释放方式
LPVOID lpMem); //释放地址
f.销毁堆
BOOL HeapDestroy( HANDLE hHeap ); //堆句柄
当堆被销毁后,使用该堆分配的内存全部被销毁
(3)VirtualAlloc/HeapAlloc/malloc/new在windows平台上函数的调用关系
malloc/new -> HeapAlloc -> VirtualAlloc
相关代码:
#include "stdafx.h" #include "windows.h" #include "stdio.h" void HeapInfo() { HANDLE hHeap = GetProcessHeap(); printf("第一个堆:%p ", hHeap); HANDLE hHeaps[256] = { 0 }; DWORD nCount = GetProcessHeaps(256, hHeaps); for (DWORD i = 0; i < nCount; i++) { printf("%d-%d ", i, hHeaps[i]); } } void Heap() { //创建1M大小的堆 HANDLE hHeap = HeapCreate(HEAP_NO_SERIALIZE, 1024 * 1024, 0); //获取堆中某部分的地址 char *pszText = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 2*1024*1024); strcpy_s(pszText, 11, "hello heap"); printf("%s ", pszText); //释放内存 HeapFree(hHeap, 0, pszText); //销毁内存 HeapDestroy(hHeap); } int _tmain(int argc, _TCHAR* argv[]) { HeapInfo(); Heap(); return 0; }
运行结果:
5.栈内存
栈内存:每个线程都具有自己的栈,默认大小为1M。
一般是系统维护栈,windows提供了_alloca,可在栈上分配内存。
6.内存映射文件(主要作用是进程间通信)
(1)内存映射文件
将文件映射成内存来使用,当使用内存时,就是在使用文件。
(2)内存映射文件的使用
a.创建或打开文件 CreateFile
b.创建内存映射文件
HANDLE CreateFileMapping( HANDLE hFile, //文件句柄
LPSECURITY_ATTRIBUTES lpAttributes, //安全属性
DWORD flProtect, //访问方式
DWORD dwMaximumSizeHigh, //内存映射文件大小的高32位
DWORD dwMaximumSizeLow, //内存映射文件大小的di32位
LPCTSTR lpName); //命名,可以为NULL(想进程通信,必须给出文件名)
创建成功返回句柄
(2)加载内存映射文件(将硬盘文件和本进程地址建立映射关系)
LPVOID MapViewOfFile( HANDLE hFileMappingObject, //映射文件句柄
DWORD dwDesiredAcess, //访问模式
DWORD dwFileOffsetHigh, //偏移量的高32位
DWORD dwFileOffsetLow, //偏移量的低32位
SIZE_T dwNumberOfBytesToMap); //映射的字节数
成功返回地址
dwFileOffsetHigh和dwFileOffsetLow是合成的偏移量,必须是区域粒度的整数倍(64K的整数倍)
(3)使用内存
(4)卸载内存映射文件(解除映射关系)
BOOL UnmapViewOfFile( LPCVOID lpBaseAddress ); //卸载地址
(5)关闭内存映射文件(删除内存映射文件这个结构)
CloseHandle
(6)关闭文件
CloseHandle
补充:HANDLE OpenFileMapping ( DWORD dwDesiredAcess, //访问方式
BOOL bInheritHandle, //继承标识
LPCTSTR lpName); //内存映射文件名称
返回内存映射文件句柄
相关代码:
(1)写进程
#include "stdafx.h" #include "windows.h" #include "stdio.h" int _tmain(int argc, _TCHAR* argv[]) { //创建硬盘文件 HANDLE hFile = CreateFile("d:/map.dat", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //创建内存映射文件 //如果第一个参数为NULL,则申请指定大小的内存 //如果第一个参数位hFile,将硬盘文件扩至指定大小 HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 1024 * 1024, "chan"); //得到内存映射文件地址(将本进程地址和内存/硬盘文件建立映射关系) char *pszText = (char*)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 64 * 1024, 0); strcpy_s(pszText, 10, "hello map"); printf("%s ", pszText); //卸载映射文件 UnmapViewOfFile(pszText); getchar(); //关闭映射文件 CloseHandle(hMap); //一旦关闭就没有了 CloseHandle(hFile); return 0; }
运行结果:
(2)读进程
#include "stdafx.h" #include "windows.h" #include "stdio.h" int _tmain(int argc, _TCHAR* argv[]) { HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "chan"); char *pszText = (char*)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 64 * 1024, 0); printf("%s ", pszText); UnmapViewOfFile(pszText); CloseHandle(hMap); return 0; }
运行结果: