内存分配方法比较
在Windows平台进行C/C++编程,你可能写过或见过N多进行内存分配的方法,常见的如malloc和new,或Windows API中的GlobalAlloc、LocalAlloc、HeapAlloc等等。
那它们各自的含义以及它们之间的区别是什么呢?
总览
- GlobalAlloc、LocalAlloc、HeapAlloc、VirtualAlloc都是Windows API,都只能在Windows平台使用
- malloc是C标准库的一部分,依赖于不同平台的libc实现
- new是运算符,依赖于编译器的实现
- 他们之前的关系引用MSDN的一张图说明:
new调用malloc, malloc调用Heap Memory API(HeapAlloc), 堆函数调用Virtual Memory API(VirtualAlloc)。
GlobalAlloc & LocalAlloc
1 | DECLSPEC_ALLOCATOR HGLOBAL GlobalAlloc( |
16-bit
GlobalAlloc和LocalAlloc是16位Windows操作系统时代的产物。现在已不推荐使用。
Intel 8086时代,内存采用段式访问,访问本段的数据或指令时,不需要变更段地址(意味着段地址寄存器不需修改)。而访问本段范围以外的数据或指令时,则需要变更段地址(意味着段地址寄存器需要修改)。
因此,在16位处理器环境下,如果访问本段内(64K范围内)地址的值,用一个16位的指针(表示段内偏移)就可以访问到;而要访问本段以外地址的值,则需要用16位的段内偏移+16位的段地址,总共32位的指针。
也就是我们所说的近指针(near pointer)和远指针(far pointer)。
举个例子,如果你有一个近指针p,它的值是0x1234,然后数据段的值为0x012F,则当你写入数据到*p时,你即将访问的地址为012F:1234。
当我们定义指针时,默认是近指针,如果需要定义远指针,需要特别标识。
1 | char near *p; // default |
GlobalAlloc这个函数会分配一个段选择子(selectors),进而通过它来访问你请求的内存地址。你可以使用这个段选择子来声明一个远指针。一个远指针包括一个段选择子和一个近指针。
在16位Windows上,LocalAlloc和GlobalAlloc有着完全不同的行为。LocalAlloc会返回一个近指针,而GlobalAlloc会返回一个段选择子。
32-bit ~
32位Windows操作系统之后,线性内存模型(linear memory model)更广泛的被应用。在这种模式下,应用程序看到的内存是一个单独的连续地址空间。CPU可以直接(且线性)寻址所有可利用的内存位置,无需诉诸任何内存分段或分页机制。
从32位 Windows 开始, GlobalAlloc和LocalAlloc的实现都为HeapAlloc。
1 | ::HeapAlloc(::GetProcessHeap(), ...); |
所以在32位操作系统之后,GlobalAlloc和LocalAlloc几乎没有区别。
HeapAlloc
1 | DECLSPEC_ALLOCATOR LPVOID HeapAlloc( |
上面提到的GlobalAlloc和LocalAlloc在32位系统之后都是使用HeapAlloc实现的,且第一个参数都是GetProcessHeap的结果,即进程的缺省堆。
一般情况下,你并不需要使用HeapAlloc,除非你需要自己管理堆,或者避免使用CRT(c-runtime-library)。
VirtualAlloc
1 | LPVOID VirtualAlloc( |
这是一个极其底层的Windows API。通常用来分配大块的内存。例如如果想在进程A和进程B之间通过共享内存的方式实现通信,可以使用该函数(这也是较常用的情况)。不要用该函数实现通常情况的内存分配。
它在虚拟地址空间中预定一块内存区域,然后调拨物理存储器。
malloc & new
1 | void* malloc( size_t size ); |
malloc是运行时依赖的,是C标准库的一部分。
1 | ::(optional) new ( type ) initializer(optional) (1) |
new是编译器依赖和语言依赖的。也就是说,它需要一个编译C++的编译器。通常情况下,它是由malloc实现的,所以也可以说new是运行时依赖的。
上面讲的malloc和new本质上的区别。下面是一些应用场景的区别:
(0)new/delete是运算符,malloc/free是库函数。
(1)申请的内存所在位置
- new 操作符是从自由存储区(free store)上为对象动态分配内存空间。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。
- malloc函数是从堆上动态分配内存。堆是操作系统中的术语。
(2)是否需要指定内存大小
- new不需要,编译器会自行计算。
- malloc需要。
(3)返回类型
- new内存分配成功时,返回的是对象类型的指针。
- malloc返回的是void *。
(4)分配失败
- new分配失败会抛出bad_alloc异常。
- malloc分配内存失败返回NULL。
(5)重载
new/delete操作符可以重载。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Foo {
public:
void* operator new(size_t size)
{
std::cout << "Overloading new operator" << std::endl;
void* p = ::operator new(size);
return p;
}
void operator delete(void* p)
{
std::cout << "Overloading delete operator" << std::endl;
}
};
int main()
{
Foo* foo = new Foo;
delete foo;
}malloc/free不允许。
Reference
- https://docs.microsoft.com/en-us/windows/win32/memory/comparing-memory-allocation-methods?redirectedfrom=MSDN
- https://stackoverflow.com/questions/34326835/localalloc-vs-globalalloc-vs-malloc-vs-new
- https://stackoverflow.com/questions/8224347/malloc-vs-heapalloc
- https://devblogs.microsoft.com/oldnewthing/20041101-00/?p=37433