跳转到内容

Windows Programming/内存子系统

维基教科书,自由的教学读本

在Windows内核之上是虚拟内存管理API与内存映射文件。在虚拟内存管理是堆管理与C运行时(CRT)的内存管理函数。

堆管理

[编辑]

GlobalAlloc与LocalAlloc是等价的。二者在进程缺省堆上利用HeapAlloc分配内存,但用句柄访问,因此是可重定位内存块,但比HeapAlloc慢很多。推荐使用HeapAlloc。VirtualAlloc更为基础,有更多选项,是在“页”粒度上分配内存。

GlobalAlloc与LocalAlloc分配的固定内存块,其返回值可作为内存地址立即使用;如果分配的是可移动内存,必须用GlobalLock与LocalLock锁定后才能访问。

“堆”分为默认堆和私有堆两种。默认堆是在程序初始化时由操作系统自动创建的,所有C语言标准内存管理函数都是在默认堆中申请内存的;而私有堆相当于在默认堆中保留了一大块内存,用堆管理函数可以在这个保留的内存区域中分配内存。一个进程的默认堆只有一个,而私有堆可以被创建多个。默认堆可以直接被使用,而私有堆在使用前需要先创建。使用私有堆有很多好处:

  • 可以使用默认堆的函数有多种,而它们可能在不同的线程中同时对默认堆进行操作,为了保持堆管理的同步,对默认堆的访问是串行的;而私有堆的空间是预留的,不同线程在不同的私有堆中同时分配内存并不会引起冲突,可以并发访问各自的私有堆。
  • 当系统必须在物理内存和页文件之间进行页面交换时,系统的性能会受到很大的影响,在某些情况下,使用私有堆可以防止系统频繁地在物理内存和交换文件之间进行数据交换,因为将经常访问的内存局限在一个小范围地址的话,页面交换就不大可能发生,把频繁访问的大量小块内存放在同一个私有堆中就可以保证它们在内存中的位置更近。
  • 使用私有堆有利于封装和保护模块化的程序。当程序包含多个模块时,如果使用标准内存管理函数在默认堆中分配内存,那么所有模块分配的内存块是交叉排列在一起的,如果模块A中的一个错误导致内存操作越界,可能会覆盖模块B使用的内存块,到模块B执行时出错,我们将很难发现错误的源头来自于模块A。而如果让不同模块使用自己的私有堆,那么它们的内存就会完全隔离开了,越界错误就很容易跟踪和定位了。
  • 使用私有堆使得大量内存的清理变得方便,在默认堆中分配的内存需要一块块单独释放,但将一个私有堆释放后,在这个堆里的内存就全部被释放掉了。并不需要预先释放堆栈的每个内存块。

创建私有堆的函数:

 HANDLE WINAPI HeapCreate(
  __in  DWORD flOptions, //指定堆的属性:
                         //HEAP_GENERATE_EXCEPTIONS---指定函数失败时返回值,不指定这个标志时,函数失败时返回NULL、否则返回一个具体的错误代码
                         //HEAP_NO_SERIALIZE---控制对私有堆的访问是否要进行独占性的检测;指定这个标志时,建立堆时不进行独占性检测,访问速度可以更快
  __in  SIZE_T dwInitialSize, //创建堆时分配给堆的物理内存(堆的内存不足时可以自动扩展)
  __in  SIZE_T dwMaximumSize //能够扩展到的最大物理内存
);

如果一个堆不再需要了,可以调用HeapDestroy函数将它释放:

BOOL WINAPI HeapDestroy(
  __in  HANDLE hHeap //堆句柄
);

释放私有堆可以释放堆中包含的所有内存块,也可以将堆占用的物理内存和保留的地址空间全部返还给系统。如果函数运行成功,返回值是TRUE;当在进程终止时没有调用HeapDestry函数将私有堆释放时,系统会自动释放。

在堆中分配内存块,使用HeapAlloc函数:

LPVOID WINAPI HeapAlloc(
  __in  HANDLE hHeap, //堆句柄
  __in  DWORD dwFlags, //以下标识的组合:HEAP_NO_SERIALIZE       HEAP_GENERATE_EXCEPTIONS      HEAP_ZERO_MEMORY
  __in  SIZE_T dwBytes //需要分配的内存块的字节数
);

注意,在堆中分配的内存块只能是固定的内存块,不想GlobalAlloc函数一样可以分配可移动的内存块。

如果要释放分配到的内存块,使用HeapFree函数:

BOOL WINAPI HeapFree(
  __in  HANDLE hHeap, //堆句柄
  __in  DWORD dwFlags,
  __in  LPVOID lpMem //要释放的内存块指针,由HeapAlloc或HeapReAlloc返回
);

对于用HeapAlloc分配的内存块,可以使用HeapReAlloc函数重新调整大小:

LPVOID WINAPI HeapReAlloc(
  __in  HANDLE hHeap, //堆句柄
  __in  DWORD dwFlags, //以下标识的组合:HEAP_NO_SERIALIZE | HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY | HEAP_REALLOC_IN_PLACE_ONLY
  __in  LPVOID lpMem, //内存块指针
  __in  SIZE_T dwBytes //新的大小
);

其他堆管理函数:

GetProcessHeaps函数用来列出进程中所有的堆:

DWORD WINAPI GetProcessHeaps(
  __in   DWORD NumberOfHeaps, //缓冲区中可以存放句柄的数量
  __out  PHANDLE ProcessHeaps //指向用来接收堆句柄的缓冲区的指针,缓冲区的长度应该等于
                                               //NumberOfHeaps*4字节
);

函数执行后,进程中所有堆的句柄全部列在缓冲区中,其中也包括默认堆的句柄。

GetProcessHeap函数用来获得进程默认堆的句柄:

HANDLE WINAPI GetProcessHeap(void);

HeapWalk函数用来列出一个堆中所有内存块:

BOOL WINAPI HeapWalk(
  __in     HANDLE hHeap, //堆句柄
  __inout  LPPROCESS_HEAP_ENTRY lpEntry //指向一个包含PROCESS_HEAP_ENTRY结构的缓冲区
);

调用HeapWalk函数时,函数每次在PROCESS_HEAP_ENTRY结构中返回一个内存块的信息,如果还有其他内存块,函数返回TRUE,程序可以一直循环调用HeapWalk函数直到函数返回FALSE为止。在多线程程序中使用HeapWalk,必须首先使用HeapLock函数将堆锁定,否则调用会失败。其中PROCESS_HEAP_ENTRY结构如下,它包含了堆元素的信息:

typedef struct _PROCESS_HEAP_ENTRY {
  PVOID lpData; //指向堆元素数据区的指针,初始调用HeapWalk时,应将lpData设为NULL
  DWORD cbData; //堆元素数据区的字节大小
  BYTE  cbOverhead; //
  BYTE  iRegionIndex; //
  WORD  wFlags; //
  union {
    struct {
      HANDLE hMem; //
      DWORD  dwReserved[3]; //
    } Block;
    struct {
      DWORD  dwCommittedSize; //
      DWORD  dwUnCommittedSize; //
      LPVOID lpFirstBlock; //
      LPVOID lpLastBlock; //
    } Region;
  } ;
} PROCESS_HEAP_ENTRY, *LPPROCESS_HEAP_ENTRY;

HeapValidate函数用来验证堆的完整性或堆中某个内存块的完整性:

BOOL WINAPI HeapValidate(
  __in      HANDLE hHeap, //要验证的堆句柄
  __in      DWORD dwFlags, //标志位
  __in_opt  LPCVOID lpMem //为NULL,则函数顺序验证堆中所有的内存块;
                                     //指定一个内存块,则只验证这个内存块
);

如果验证结果是内存块都完好无损,函数返回非0值,否则函数返回0。

HeapLock和HeapUnlock函数用来锁定和解锁堆,这两个函数用于线程的同步:

BOOL WINAPI HeapLock(
  __in  HANDLE hHeap
);

BOOL WINAPI HeapUnlock(
  __in  HANDLE hHeap
);

如果函数执行成功,返回值是非0值,否则返回0。一般来说,很少在程序中使用这两个函数,而总是使用HEAP_NO_SERIALIZE标志来进行同步控制,指定了这个标志的话,HeapAlloc、HeapReAlloc、HeapSize和HeapFree等函数会在内部自己调用HeapLock和HeapUnlock函数。

HeapSize函数返回堆中某个内存块的大小,这个大小就是使用HeapAlloc以及HeapReAlloc时指定的大小:

SIZE_T WINAPI HeapSize(
  __in  HANDLE hHeap, //堆句柄
  __in  DWORD dwFlags, //标志位
  __in  LPCVOID lpMem //需要返回大小的内存块
);

HeapCompact函数用于合并堆中的空闲内存块并释放不在使用中的内存页面:

SIZE_T WINAPI HeapCompact(
  __in  HANDLE hHeap,
  __in  DWORD dwFlags
);

下面实例代码使用HeapWalk函数来遍历一个堆:

#include <windows.h>
#include <tchar.h>
#include <stdio.h>

int __cdecl _tmain()
{
    DWORD LastError;
    HANDLE hHeap;
    PROCESS_HEAP_ENTRY Entry; 

    //
    // Create a new heap with default parameters.
    //

    hHeap = HeapCreate(0, 0, 0);
    if (hHeap == NULL) {
        _tprintf(TEXT("Failed to create a new heap with LastError %d./n"),GetLastError());
        return 1;
    }

    //
    // Lock the heap to prevent other threads from accessing the heap during enumeration.
    //
    if (HeapLock(hHeap) == FALSE) {
        _tprintf(TEXT("Failed to lock heap with LastError %d./n"),GetLastError());
        return 1;
    } 

    _tprintf(TEXT("Walking heap %#p.../n/n"), hHeap);
    Entry.lpData = NULL;
    while (HeapWalk(hHeap, &Entry) != FALSE) {
        if ((Entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
            _tprintf(TEXT("Allocated block"));
            if ((Entry.wFlags & PROCESS_HEAP_ENTRY_MOVEABLE) != 0) {
                _tprintf(TEXT(", movable with HANDLE %#p"), Entry.Block.hMem);
            }
            if ((Entry.wFlags & PROCESS_HEAP_ENTRY_DDESHARE) != 0) {
                _tprintf(TEXT(", DDESHARE"));
            }
        }
        else if ((Entry.wFlags & PROCESS_HEAP_REGION) != 0) {
            _tprintf(TEXT("Region/n  %d bytes committed/n") /
                     TEXT("  %d bytes uncommitted/n  First block address: %#p/n") /
                     TEXT("  Last block address: %#p/n"),
                     Entry.Region.dwCommittedSize,
                     Entry.Region.dwUnCommittedSize,
                     Entry.Region.lpFirstBlock,
                     Entry.Region.lpLastBlock);
        }
        else if ((Entry.wFlags & PROCESS_HEAP_UNCOMMITTED_RANGE) != 0) {
            _tprintf(TEXT("Uncommitted range/n"));
        }
        else {
            _tprintf(TEXT("Block/n"));
        }

        _tprintf(TEXT("  Data portion begins at: %#p/n  Size: %d bytes/n") /
                 TEXT("  Overhead: %d bytes/n  Region index: %d/n/n"),
                 Entry.lpData,
                 Entry.cbData,
                 Entry.cbOverhead,
                 Entry.iRegionIndex);
    }
    LastError = GetLastError();
    if (LastError != ERROR_NO_MORE_ITEMS) {
        _tprintf(TEXT("HeapWalk failed with LastError %d./n"), LastError);
    }

    //
    // Unlock the heap to allow other threads to access the heap after enumeration has completed.
    //
    if (HeapUnlock(hHeap) == FALSE) {
        _tprintf(TEXT("Failed to unlock heap with LastError %d./n"),GetLastError());
    } 

    //
    // When a process terminates, allocated memory is reclaimed by the operating
    // system so it is not really necessary to call HeapDestroy in this example.
    // However, it may be advisable to call HeapDestroy in a longer running
    // application.
    //
    if (HeapDestroy(hHeap) == FALSE) {
        _tprintf(TEXT("Failed to destroy heap with LastError %d./n"),GetLastError());
    }

    return 0;
}


下面的实例代码使用GetProcessHeaps函数获得进程中所有堆的句柄:

#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <intsafe.h>

int __cdecl _tmain()
{
    DWORD NumberOfHeaps;
    DWORD HeapsIndex;
    DWORD HeapsLength;
    HANDLE hDefaultProcessHeap;
    HRESULT Result;
    PHANDLE aHeaps;
    SIZE_T BytesToAllocate;
 
    //
    // Retrieve the number of active heaps for the current process
    // so we can calculate the buffer size needed for the heap handles.
    //
    NumberOfHeaps = GetProcessHeaps(0, NULL);
    if (NumberOfHeaps == 0) {
        _tprintf(TEXT("Failed to retrieve the number of heaps with LastError %d./n"),GetLastError());
        return 1;
    } 

    //
    // Calculate the buffer size.
    //
    Result = SIZETMult(NumberOfHeaps, sizeof(*aHeaps), &BytesToAllocate);
    if (Result != S_OK) {
        _tprintf(TEXT("SIZETMult failed with HR %d./n"), Result);
        return 1;
    }

    //
    // Get a handle to the default process heap.
    //
    hDefaultProcessHeap = GetProcessHeap();
    if (hDefaultProcessHeap == NULL) {
        _tprintf(TEXT("Failed to retrieve the default process heap with LastError %d./n"),GetLastError());
        return 1;
    } 

    //
    // Allocate the buffer from the default process heap.
    //
    aHeaps = (PHANDLE)HeapAlloc(hDefaultProcessHeap, 0, BytesToAllocate);
    if (aHeaps == NULL) {
        _tprintf(TEXT("HeapAlloc failed to allocate %d bytes./n"),BytesToAllocate);
        return 1;
    } 

    //
    // Save the original number of heaps because we are going to compare it
    // to the return value of the next GetProcessHeaps call.
    //
    HeapsLength = NumberOfHeaps;

    //
    // Retrieve handles to the process heaps and print them to stdout.
    // Note that heap functions should be called only on the default heap of the process
    // or on private heaps that your component creates by calling HeapCreate.
    //
    NumberOfHeaps = GetProcessHeaps(HeapsLength, aHeaps);
    if (NumberOfHeaps == 0) {
        _tprintf(TEXT("Failed to retrieve heaps with LastError %d./n"),GetLastError());
        return 1;
    }
    else if (NumberOfHeaps > HeapsLength) { 

        //
        // Compare the latest number of heaps with the original number of heaps.
        // If the latest number is larger than the original number, another
        // component has created a new heap and the buffer is too small.
        //
        _tprintf(TEXT("Another component created a heap between calls. ") /
                 TEXT("Please try again./n"));
        return 1;
    } 

    _tprintf(TEXT("Process has %d heaps./n"), HeapsLength);
    for (HeapsIndex = 0; HeapsIndex < HeapsLength; ++HeapsIndex) {
        _tprintf(TEXT("Heap %d at address: %#p./n"),
                 HeapsIndex,
                 aHeaps[HeapsIndex]);
    } 

    //
    // Release memory allocated from default process heap.
    //
    if (HeapFree(hDefaultProcessHeap, 0, aHeaps) == FALSE) {
        _tprintf(TEXT("Failed to free allocation from default process heap./n"));
    } 

    return 0;
}

CString的安全使用

[编辑]
CWin32Heap stringHeap( HEAP_NO_SERIALIZE, 0, 0 );
CAtlStringMgr stringMgr( &stringHeap );
CString strstate(&stringMgr );
strstate.Format("正在解密,请稍后... (共 %d 张地图)",p->m_countmap);