最近在学习 Win32编程,所以顺便将每日所学记录下来,一方面为了巩固学习的知识,另一方面也为同样在学习Win32开发的童鞋们提供一份参考。
本系列博文均根据学习《WindowsAPI开发详解》一书总结而来;
运行环境:
操作系统: Windows 10家庭版
编译器:Visual Studio 2013
内存管理 内存主要存储着程序运行时所需的机器代码数据等。
内存管理原理 数据是存储与内存中,为了能够找到存储与内存中的数据,内存的最小存储单元是字节,内存中的每一个字节都有一个地址;
基本概念
地址空间
物理内存
硬件系统中真实存在的内存称为物理内存,物理内存的访问必须通过硬件系统总线进行;
虚拟地址空间
为了访问内存的统一,操作系统允许其运行的程序访问所有的4GB内存空间的地址。
进程内存空间
Windows操作系统每个进程都有属于自己的虚拟地址空间。32位系统上将4GB的虚拟内存划分为两个部分,进程使用低2gb,内存使用搞2gb。虚拟地址空间在进程上是封闭的,进程只能访问自己的地址空间。
分页与分段内存管理 X86体系CPU中,有若干的段寄存器,可以通过“选择器+偏移”的形式来标识内存,使用这种方式标识的地址称”逻辑地址”,这种机制称为”分段”。如果使用了分页,线性地址与物理的对应是通过分页的机制实现的。通常情况下,页大小为4KB;
进程的内存空间
进程虚拟地址空间
Windows系统中每个进程都有一个私有的虚拟地址空间,系统需要将每个进程的虚拟地址空间都映射到物理内存地址上。为了实现系统每个进程都有一个私有的虚拟地址空间,系统为每个进程都创建一个页目录和一组页表。每个进程的页表是独立的,而内核空间的页表是所有进程共享的;
数据保护共享
在一个进程间共享数据、系统的可执行代码,在各个进程间都是一致的,因此没有必要再物理内存中为这些数据保留多分,不同进程的虚拟内存分页可以映射为同样的物理内存分页。
为了保证映射到相同物理内存页上的内存分页在进程上是私有的,系统还提供了一些保护机制;如果对DLL中的数据进行写操作,那么在数据写入之前,将要写入的进程虚拟内存分页映射到一个新的内存分页。并将源系统DLL的内容复制到这个分页中,进程不共享这个新乌里内存分页,最后进程结束,将数据写入这个新的分页中;
虚拟内存布局 进程的虚拟内存空间分为两部分,低2gb由应用程序使用,高2gb由系统内核使用;
应用程序壳可使用的低地址空间中包括了应用程序代码、数据、系统和用户DLL的代码、各线程的栈、堆等;
进程的每个线程都有自己的栈,栈与函数的调用、执行和返回及局部变量的保存相关;
内存保护属性和存储权限 系统为每个内存分页提供保护属性和存取权限,内存的保护属性和存取权限的最小单位是分页,也就是说同一个分页中的内存必然具有相同的保护属性和存取权限;
堆管理 用户使用内存分配函数分配的内存都位于堆中,所以使用堆管理函数内存进行分配、释放是最为直接的方式;
获取堆句柄、分配与再分配 堆是一种内存管理对象,一个进程有若干个堆,在分配内存前需要指定从哪个堆上进行分配。对的句柄唯一标识了一个堆。在堆上分配内容前,需要获得索要进行分配的堆的句柄,获得堆的句柄有两种方式:获取在进程中时已经创建好的堆,进程自己再创建堆
HeapCreate 为进程创建新堆,请求分配虚拟内存分页;
1 2 3 HANDLE HeapCreate (DWORD flOptions, DWORD dwInitialSize, DWORD dwMaximumSize ) ;
GetProcessHeap 1 HANDLE GetProcessHeap (VOID) ;
GetProcessHeaps 获取进程中所有堆,包括堆的数量和各个堆的句柄
1 2 DWORD GetProcessHeaps ( DWORD NumberOfHeaps, PHANDLE ProcessHeaps
HeapAlloc 从指定的堆上分配内存块
1 2 3 LPVOID HeapAlloc ( HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes
HeapReAlloc 重新分配内存,改变已经分配好的堆内存块的大小
1 2 3 4 LPVOID HeapReAlloc (HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, DWORD dwBytes ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <Windows.h> #include <tchar.h> #include <stdio.h> DWORD PrintHeapSize (HANDLE hHeap, LPVOID lpMem) { SIZE_T dwHeapSize; dwHeapSize = HeapSize(hHeap, HEAP_NO_SERIALIZE, lpMem); if (dwHeapSize == -1 ) { printf ("Get HeapSize error :%d" , GetLastError()); return 1 ; } printf ("内存块大小为:0x%x\n" , dwHeapSize); return 0 ; } int main () { SYSTEM_INFO si; HANDLE hHeap; HANDLE hHeap1; LPVOID lpMem; LPVOID lpReAlloc; DWORD dwHeapSize; HANDLE hHeaps[MAX_PATH]; DWORD dwHeapNum; GetSystemInfo(&si); printf ("系统内存页大小: 0x%x\n系统内存分配粒度:0x%x\n" , si.dwPageSize, si.dwAllocationGranularity); hHeap = HeapCreate(HEAP_NO_SERIALIZE, si.dwPageSize, si.dwPageSize * 10 ); printf ("创建堆,初始化大小为1页,最大为10页\n" ); hHeap = GetProcessHeap(); printf ("获取系统已经存在的堆\n" ); hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0 , 0 ); printf ("创建堆,初始化大小为1页,大小可变\n" ); dwHeapNum = GetProcessHeaps(MAX_PATH, hHeaps); printf ("当前进程一共有%d个堆\n" , dwHeapNum); lpMem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, si.dwPageSize * 3 ); printf ("在堆上成功分配内存,起始地址为:0x%x\n" , lpMem); PrintHeapSize(hHeap, lpMem); lpReAlloc = HeapReAlloc(hHeap, HEAP_ZERO_MEMORY, lpMem, si.dwPageSize * 11 ); printf ("在堆上再分配内存,地址为:0x%x,原地址:0x%x\n" , lpReAlloc, lpMem); PrintHeapSize(hHeap, lpReAlloc); if (!HeapFree(hHeap, HEAP_NO_SERIALIZE, lpReAlloc)) { printf ("HeapFree error: %d" , GetLastError()); return 1 ; } printf ("释放内存成功\n" ); getchar(); return 0 ; }
堆内存块大小信息 HeapSize 1 2 3 DWORD HeapSize ( HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem ) ;
demo:
1 2 3 4 5 6 7 8 9 10 11 12 DWORD PrintHeapSize (HANDLE hHeap,LPVOID lpMem) { SIZE_T dwHeapSize; dwHeapSize = HeapSize(hHeap,HEAP_NO_SERIALIZE,lpMem); if (dwHeapSize == -1 ) { printf ("Get HeapSize error :%d" ,GetLastError()); return 1 ; } printf ("内存块大小为:0x%x\n" ,dwHeapSize); return 0 ; }
释放内存销毁堆 分配的内存再使用完成后需要释放,否则内存将不能再次使用,造成内存泄漏
HeapFree 1 2 3 BOOL HeapFree (HANDLE hHeap, DWORD dwFlags, LPVOID lpMem ) ;
HeapDestroy 1 BOOL HeapDestroy ( HANDLE hHeap
虚拟内存管理 虚拟地址空间与内存分页 进程的虚拟地址空间内存页面存在三种状态,分别为空闲的、保留的、提交的。一个页大小4KB
分配和释放可读写的虚拟内存页 使用VitrualAlloc和VirtualAllocEx分配虚拟内存时,可以指定分配的内存页面是保留的还是提交的。
VirtualAlloc 1 2 3 4 LPVOID VirtualAlloc ( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect
VirtualAllocEx 1 2 3 4 5 LPVOID VirtualAllocEx ( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect
VirtualFree 1 2 3 BOOL VirtualFree ( LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType
VirtualQuery 使用VirtualQuery获取内存信息
1 2 3 DWORD VirtualQuery ( LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <windows.h> #include <stdio.h> int main (void ) { SIZE_T sizeVirtual = 4000 ; LPVOID lpRound = (LPVOID)0x100000FF ; MEMORY_BASIC_INFORMATION mbi; LPVOID lpAddress = VirtualAlloc( lpRound, sizeVirtual, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE ); if (lpAddress == NULL ) { printf ("VirtualAlloc error: %d\n" , GetLastError()); return 1 ; } printf ("Alloc:MEM_COMMIT|MEM_RESERVE\n" ); CopyMemory(lpAddress, "hello,world" , strlen ("hello,world" ) + 1 ); printf ("分配、复制成功,地址:0x%.8x,内容:%s\n" , lpAddress, lpAddress); VirtualQuery(lpAddress, &mbi, sizeof (mbi)); printf ("使用VirtualQuery获得的信息:\n" "BaseAddress:0x%.8x\tAllocationBase:0x%.8x\t" "AllocationProtect:0x%.8x\tRegionSize:%u\t" "State:0x%.8x\tProtect:0x%.8x\tType:0x%.8x\n" , mbi.BaseAddress, mbi.AllocationBase, mbi.AllocationProtect, mbi.RegionSize, mbi.State, mbi.Protect, mbi.Type ); printf ("Free: DECOMMIT\n" ); if (!VirtualFree(lpRound, sizeVirtual, MEM_DECOMMIT)) { printf ("VirtualFree error: %d" , GetLastError()); return 1 ; } VirtualQuery(lpAddress, &mbi, sizeof (mbi)); printf ("使用VirtualQuery获得的信息:\n" "BaseAddress:0x%.8x\tAllocationBase:0x%.8x\t" "AllocationProtect:0x%.8x\tRegionSize:%u\t" "State:0x%.8x\tProtect:0x%.8x\tType:0x%.8x\n" , mbi.BaseAddress, mbi.AllocationBase, mbi.AllocationProtect, mbi.RegionSize, mbi.State, mbi.Protect, mbi.Type ); printf ("Free:RELEASE\n" ); if (!VirtualFree(lpAddress, 0 , MEM_RELEASE)) { printf ("VirtualFree error: %d" , GetLastError()); return 1 ; } getchar(); return 0 ; }
运行结果:
虽然我们是从0x0x100000FF开始分配,但是Windows自动对齐了,所以我们访问的是0x1000000;
MEMORY_BASIC_INFORMATION结构体:
1 2 3 4 5 6 7 8 9 10 11 typedef struct _MEMORY_BASIC_INFORMATION { PVOID BaseAddress; PVOID AllocationBase; DWORD AllocationProtect; SIZE_T RegionSize; DWORD State; DWORD Protect; DWORD Type; } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
修改内存页状态 VirtualProtect 1 2 3 4 BOOL VirtualProtect ( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect
VirtualLock 1 2 BOOL VirtualLock ( LPVOID lpAddress, SIZE_T dwSize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
内存操作与内存信息管理 复制、清空内存块、防止溢出 CopyMemory 复制内存
1 2 3 VOID CopyMemory ( PVOID Destination, CONST VOID* Source, SIZE_T Length
FillMemoey 填充内存,将一段内存填充为某一个值
1 2 3 4 VOID FillMemory ( PVOID Destination, memory block SIZE_T Length, ) ;
MoveMemory 复制内存
1 2 3 VOID MoveMemory ( PVOID Destination, CONST VOID *Source, SIZE_T Length
源地址与目的地址可相同
ZeroMemory 清空指定内存
1 2 VOID ZeroMemory ( PVOID Destination, SIZE_T Length
demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <windows.h> #include <stdio.h> #define MEM_BLOCK_MAX_SIZE 32 BOOL ShowMemContent (LPVOID lpMem, SIZE_T dwSize) { BYTE lpShow[MEM_BLOCK_MAX_SIZE]; INT i = 0 ; if (dwSize>MEM_BLOCK_MAX_SIZE) { printf ("over-flow" ); return FALSE; } CopyMemory((LPVOID)lpShow, lpMem, dwSize); for (; i<dwSize; i++) { printf ("%.2X " , lpShow[i]); if (!((i + 1 ) % 16 )) { printf ("\n" ); } } printf ("\n" ); return TRUE; } int main (void ) { HANDLE hHeap = GetProcessHeap(); LPVOID lpSrc; LPVOID lpDis; lpSrc = HeapAlloc(hHeap, 0 , MEM_BLOCK_MAX_SIZE); lpDis = HeapAlloc(hHeap, 0 , MEM_BLOCK_MAX_SIZE); printf ("HeapAlloc 分配但不清零:\n" ); ShowMemContent(lpDis, MEM_BLOCK_MAX_SIZE); ZeroMemory(lpDis, MEM_BLOCK_MAX_SIZE); printf ("ZeroMemory 清零:\n" ); ShowMemContent(lpDis, MEM_BLOCK_MAX_SIZE); FillMemory(lpSrc, MEM_BLOCK_MAX_SIZE, 0xBB ); FillMemory(lpSrc, MEM_BLOCK_MAX_SIZE / 2 , 0xAA ); CopyMemory(lpDis, lpSrc, MEM_BLOCK_MAX_SIZE); printf ("FillMemory 有规律的填充内存:\n" ); ShowMemContent(lpDis, MEM_BLOCK_MAX_SIZE); HeapFree(hHeap, 0 , lpSrc); HeapFree(hHeap, 0 , lpDis); getchar(); return 0 ; }
获得当前内存使用情况 GlobalMemoryStatus GlobalMemoryStatusEx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <windows.h> #include <stdio.h> #include <iostream> #include <iomanip> using namespace std ;int main () { MEMORYSTATUSEX statex; statex.dwLength = sizeof (statex); bool res = GlobalMemoryStatusEx(&statex); cout << "GlobalMemoryStatusEx(&) = " << res << endl ; cout << "GetLastError() = " << GetLastError() << endl << endl ; HANDLE hEvent; hEvent = CreateEvent(NULL , FALSE, FALSE, NULL ); const unsigned long long DIV = 1024 ; while (1 ){ WaitForSingleObject(hEvent, 1000 ); res = GlobalMemoryStatusEx(&statex); cout << left << setw(17 ) << "内存占用率:" << setw(3 ) << statex.dwMemoryLoad << "%" << endl << "============================================" << endl << setw(17 ) << "总物理内存:" << setw(10 ) << statex.ullTotalPhys / DIV << "KB 折合 " << setw(6 ) << statex.ullTotalPhys / DIV / DIV << "MB" << endl << setw(17 ) << "闲置物理内存:" << setw(10 ) << statex.ullAvailPhys / DIV << "KB 折合 " << setw(6 ) << statex.ullAvailPhys / DIV / DIV << "MB" << endl << setw(17 ) << "物理内存闲置率:" << setw(3 ) << (statex.ullAvailPhys * 100 ) / statex.ullTotalPhys << "%" << endl << "--------------------------------------------" << endl << setw(17 ) << "总页面文件:" << setw(10 ) << statex.ullTotalPageFile / DIV << "KB 折合 " << setw(6 ) << statex.ullTotalPageFile / DIV / DIV << "MB" << endl << setw(17 ) << "闲置页面文件:" << setw(10 ) << statex.ullAvailPageFile / DIV << "KB 折合 " << setw(6 ) << statex.ullAvailPageFile / DIV / DIV << "MB" << endl << setw(17 ) << "页面文件闲置率:" << setw(3 ) << (statex.ullAvailPageFile * 100 ) / statex.ullTotalPageFile << "%" << endl << "--------------------------------------------" << endl << setw(17 ) << "总虚拟内存:" << setw(10 ) << statex.ullTotalVirtual / DIV << "KB 折合 " << setw(6 ) << statex.ullTotalVirtual / DIV / DIV << "MB" << endl << setw(17 ) << "闲置虚拟内存:" << setw(10 ) << statex.ullAvailVirtual / DIV << "KB 折合 " << setw(6 ) << statex.ullAvailVirtual / DIV / DIV << "MB" << endl << setw(17 ) << "虚拟内存闲置率:" << setw(3 ) << (statex.ullAvailVirtual * 100 ) / statex.ullTotalVirtual << "%" << endl << "--------------------------------------------" << endl << setw(17 ) << "可用已扩展内存:" << setw(10 ) << statex.ullAvailExtendedVirtual / DIV << "KB 折合 " << setw(6 ) << statex.ullAvailExtendedVirtual / DIV / DIV << "MB" << endl << endl << endl ; } getchar(); return 0 ; }