跳至內容

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);