Windows 堆内存是性能仅次于虚拟内存的内存管理机制。它不像虚拟内存,每次分配至少是一个页面(4K),它可以灵活的只分配 1 个字节来使用,不浪费内存的空间。但你分配的内存必须由自己维护释放。下面演示了堆内存的使用方法。

最简单的堆使用

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
#include <Windows.h>
#include <stdio.h>

int main()
{
// 使用系统给每个进程提供的默认堆
HANDLE hHeap = GetProcessHeap();
float* fArray = (float*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 1000 * sizeof(float));
for (int i = 0; i < 1000; i++)
{
fArray[i] = 1.0f * rand();
}
HeapFree(hHeap, 0, fArray);

// 创建一个私有堆
hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0, 0);
fArray = (float*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 1000 * sizeof(float));
for (int i = 0; i < 1000; i++)
{
fArray[i] = 1.0f * rand();
}

// 类似 C 语言库函数的 realloc 函数
fArray = (float*)HeapReAlloc(hHeap, HEAP_ZERO_MEMORY, fArray, 2 * 1000 * sizeof(float));
for (int i = 0; i < 2 * 1000; i++)
{
fArray[i] = 1.0f * rand();
}

HeapFree(hHeap, 0, fArray);
// 销毁堆
HeapDestroy(hHeap);
return 0;
}

查询进程中堆内存的详细使用信息

同虚拟内存一样,堆内存也可以遍历得到每一块堆内存的使用情况,主要用到的就是 HeapWalk 函数,下面的图片介绍了不同堆类型的不同属性值,图片下面是代码实现,参考者图看代码更容易理解。 2016-04-10_185012

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 <tchar.h>
#include <windows.h>
#include <time.h>
#include <iostream>
#include <vector>

void DisplayHeapsInfo(std::ostream& out = std::cout);

int _tmain()
{
DisplayHeapsInfo();
_tsystem(_T("pause"));
return 0;
}

void DisplayHeapsInfo(std::ostream& out /*= std::cout*/)
{
// 得到有多少个堆内存块
// 初始化,设定最大元素为 GetProcessHeaps 返回的值,防止数组动态增长分配不必要的内存
std::vector<HANDLE> heaps(GetProcessHeaps(0, NULL));
// 得到首个堆的地址
GetProcessHeaps(heaps.size(), &heaps[0]);

DWORD totalBytes = 0;
for (DWORD i = 0; i < heaps.size(); i++)
{
// 依次遍历
out << "Heap handle: 0x" << heaps[i] << '\n';
PROCESS_HEAP_ENTRY phi = { 0 };
while (HeapWalk(heaps[i], &phi))
{
out << "Block Start Address: 0x" << phi.lpData << '\n';
out << "\tSize: " << phi.cbData << " - Overhead: "
<< static_cast<DWORD>(phi.cbOverhead);
out << "\tBlock is a";

// 已提交的堆虚拟内存区域
if (phi.wFlags & PROCESS_HEAP_REGION)
{
out << " VMem region:\n";
out << "\tCommitted size: " << phi.Region.dwCommittedSize << '\n';
out << "\tUnCommitted size: " << phi.Region.dwUnCommittedSize << '\n';
out << "\tFirst block: 0x" << phi.Region.lpFirstBlock << '\n';
out << "\tLast block: 0x" << phi.Region.lpLastBlock << '\n';
}
else
{
// 未提交的堆虚拟内存区域
if (phi.wFlags & PROCESS_HEAP_UNCOMMITTED_RANGE)
{
out << "n uncommitted range\n";
}
// 已经被分配的堆内存区域
else if (phi.wFlags & PROCESS_HEAP_ENTRY_BUSY)
{
totalBytes += phi.cbData;
out << "n Allocated range: Region index - "
<< static_cast<unsigned>(phi.iRegionIndex) << '\n';
if (phi.wFlags & PROCESS_HEAP_ENTRY_MOVEABLE)
{
out << "\tMoable: Handle is 0x" << phi.Block.hMem << '\n';
}
else if (phi.wFlags & PROCESS_HEAP_ENTRY_DDESHARE)
{
out << "\tDDE Sharable\n";
}
}
else
{
out << " block, no other flags specified\n";
}
}
out << std::endl;
}
}
out << "End of report - total of " << std::dec << totalBytes << " allocated" << std::endl;
}

低碎块堆

所谓低碎块堆实际就是增加了内存对齐的机制,对齐后内存最小颗粒度为 8 个字节,分配内存时,比如指定了 10 个字节,那么实际也会分配 16 (8的最小整数倍)个字节。这样做目的是为了减少内存碎块化严重而导致缺少很多连续的内存地址空间。降低了因需要合并内存碎块而造成的额外开销,从而提升了性能。以下是具体实现代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <tchar.h>
#include <windows.h>

int _tmain()
{
HANDLE hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0, 0);
ULONG HeapFragValue = 2;
if (HeapSetInformation(hHeap,
HeapCompatibilityInformation,
&HeapFragValue,
sizeof(HeapFragValue)))
{
// LFH Open,低碎块堆已经打开,其他按普通堆正常使用即可。
}
HeapDestroy(hHeap);
return 0;
}