HeapAlloc,GlobalAlloc,LocalAlloc,VirtualAlloc,malloc,new的异同

时间:2021-09-08 13:09:19

1. 首先我们来看HeapAlloc:
MSDN上的解释为:HeapALloc是从堆上分配一块内存,且分配的内存是不可移动的(即如果没有连续的空间能满足分配的大小,程序不能将其他零散的 空间利用起来,从而导致分配失败),该分配方法是从一指定地址开始分配,而不像GloabalAlloc是从全局堆上分配,这个有可能是全局,也有可能是 局部。函数原型为:
LPVOID
HeapAlloc(
    HANDLE hHeap,
    DWORD dwFlags,
   SIZE_T dwBytes
    );
hHeap是进程堆内存开始位置。
dwFlags是分配堆内存的标志。包括HEAP_ZERO_MEMORY,即使分配的空间清零。
dwBytes是分配堆内存的大小。
其对应的释放空间函数为HeapFree。
2. 再看GlobalAlloc:该函数用于从全局堆中分配出内存供程序使用,函数原型为:
HGLOBAL GlobalAlloc(
UINT uFlags,
SIZE_T dwBytes
);
uFlags参数含义
GHND   GMEM_MOVEABLE和GMEM_ZEROINIT的组合
GMEM_FIXED   分配固定内存,返回值是一个指针
GMEM_MOVEABLE   分配活动内存,在Win32中,内存块不能在物理内存中移动,但能在默认的堆中移动。返回值是内存对象的句柄,用函数GlobalLock可将句柄转化为指针
GMEM_ZEROINIT   将内存内容初始化为零
GPTR   GMEM_FIXED和GMEM_ZEROINIT的组合
一般情况下我们在编程的时候,给应用程序分配的内存都是可以移动的或者是可以丢弃的,这样能使有限的内存资源充分利用,所以,在某一个时候我们分配的那块 内存的地址是不确定的,因为他是可以移动的,所以得先锁定那块内存块,这儿应用程序需要调用API函数GlobalLock函数来锁定句柄。如下: lpMem=GlobalLock(hMem); 这样应用程序才能存取这块内存。所以我们在使用GlobalAllock时,通常搭配使用GlobalLock,当然在不使用内存时,一定记得使用 GlobalUnlock,否则被锁定的内存块一直不能被其他变量使用。
GlobalAlloc对应的释放空间的函数为GlobalFree。
3. LocalAlloc:该函数用于从局部堆中分配内存供程序使用,函数原型为:
HLOCAL LocalAlloc(
UINT uFlags,
SIZE_T uBytes
);
参数同GlobalAlloc。
在16位Windows中是有区别的,因为在16位windows用一个全局堆和局部堆来管理内存,每一个应用程序或dll装入内存时,代码段被装入全局 堆,而系统又为每个实例从全局堆中分配了一个64kb的数据段作为该实例的局部堆,用来存放应用程序的堆栈和所有全局或静态变量。而 LocalAlloc/GlobalAlloc就是分别用于在局部堆或全局堆中分配内存。
由于每个进程的局部堆很小,所以在局部堆中分配内存会受到空间的限制。但这个堆是每个进程私有的,相对而言分配数据较安全,数据访问出错不至于影响到整个系统。
而在全局堆中分配的内存是为各个进程共享的,每个进程只要拥有这个内存块的句柄都可以访问这块内存,但是每个全局内存空间需要额外的内存开销,造成分配浪费。而且一旦发生严重错误,可能会影响到整个系统的稳定。
不过在Win32中,每个进程都只拥有一个省缺的私有堆,它只能被当前进程访问。应用程序也不可能直接访问系统内存。所以在Win32中全局堆和局部堆都 指向进程的省缺堆。用LocalAlloc/GlobalAlloc分配内存没有任何区别。甚至LocalAlloc分配的内存可以被 GlobalFree释放掉。所以在Win32下编程,无需注意Local和Global的区别,一般的内存分配都等效于 HeapAlloc(GetProcessHeap(),...)。
LocalAlloc对应的释放函数为LockFree。
4. VirtualAlloc:该函数的功能是在调用进程的虚地址空间,预定或者提交一部分页,如果用于内存分配的话,并且分配类型未指定MEM_RESET,则系统将自动设置为0;其函数原型:
LPVOID VirtualAlloc(
LPVOID lpAddress, // region to reserve or commit
SIZE_T dwSize, // size of region
DWORD flAllocationType, // type of allocation
DWORD flProtect // type of access protection
);
VirtualAlloc可以通过并行多次调用提交一个区域的部分或全部来保留一个大的内存区域。多重调用提交同一块区域不会引起失败。这使得一个应用程 序保留内存后可以随意提交将被写的页。当这种方式不在有效的时候,它会释放应用程序通过检测被保留页的状态看它是否在提交调用之前已经被提交。
VirtualAlloc对应的释放函数为VirtualFree。
5.Malloc:malloc与free是C++/C语言的标准库函数,可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用 malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是 库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
6.New:new/delete是C++的运算符。可用于申请动态内存和释放内存。C++语言需要一个能完成动态内存分配和初始化工作的运算符new, 以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。C++程序经常要调用C函数,而C程序只能用malloc /free管理动态内存。new 是个操作符,和什么"+","-","="...有一样的地位.
        malloc是个分配内存的函数,供你调用的.
        new是保留字,不需要头文件支持.
        malloc需要头文件库函数支持.new 建立的是一个对象,
        malloc分配的是一块内存.
        new建立的对象你可以把它当成一个普通的对象,用成员函数访问,不要直接访问它的地址空间
        malloc分配的是一块内存区域,就用指针访问好了,而且还可以在里面移动指针.
内存泄漏对于malloc或者new都可以检查出来的,区别在于new可以指明是那个文件的那一行,而malloc没有这些信息。new可以认为是 malloc加构造函数的执行。new出来的指针是直接带类型信息的。而malloc返回的都是void指针。

 

 

 

 

 

 

用Win32SDK编程可以有多种方法分配内存   
    1.GlobalAlloc   
    2.HeapAlloc   
    3.LocalAlloc   
    4.VirtualAlloc(Ex)   
    以及标准C/C++中的   
    5.malloc   
    6.calloc   
    7.new   
用不同的方法申请了64KB大小空间,以下是返回的地址值(WindowsXPsp2):   
    LocalAlloc:         00143060   
    GlobalAlloc:       00143060   
    HeapAlloc:           00143060   
    VirtualAlloc:     00390000   
    malloc:                 00410048   
    new:                       00410048   
    可见LocalAlloc和GlobalAlloc的地址和堆分配是一样的,   
    MSDN也写着前两个函数只是为兼容16位版本而保留的,所以不建议再使用了.   
    VirtualAlloc分配比较复杂,可以设置分配内存的属性.   
    malloc和new的结果相同.   
    这样就共有3种结果了.

我简单的说几句。   
    调用关系如下     new->malloc->HeapAlloc->VirtualAlloc->驱动程序的_PageAlloc。   
    上面的调用关系是一般的WIN32编译器的调用顺序。   
    其中HeapAlloc已经实现了malloc的那些小块内存管理,以Heap开头的有一系列函数,你会发现它与malloc/free等可以对应起来。   
    但是Borland和VC编译器稍有不同,它们选择了自己实现了malloc,而不是直接对Heap*函数做了简单包装。   
    当然,有些C++编译器new并不是先调用malloc,然后调用构造函数,但总的来说,内存的调用顺序如上。   
   
    LocalAlloc和GlobalAlloc是遗留函数。

在WindowsXP中跟踪了一下:   
    malloc(msvcrt.malloc)和new(msvcrt.??2@YAPAXI@Z)的函数体大部分(主要是核心部分)是相同的,所以结果分配也是相同的.   
    这两个函数都是调用kernel32.HeapAlloc实现的.   
    
    以下是我跟踪出来的详细调用关系:   
    msvcrt.malloc                       =>     kernel32.HeapAlloc(ntdll.RtlAllocateHeap)   
    msvcrt.??2@YAPAXI@Z           =>     kernel32.HeapAlloc(ntdll.RtlAllocateHeap)   
    kernel32.LocalAlloc           =>     ntdll.RtlAllocateHeap   
    kernel32.GlobleAlloc         =>     ntdll.RtlAllocateHeap   
    kernel32.HeapAlloc             ==     ntdll.RtlAllocateHeap   
    kernel32.VirtualAlloc       =>     kernel32.VirtualAllocEx   
    kernel32.VirtualAllocEx=>     ntdll.NtAllocateVirtualMemory   
    ntdll.RtlAllocateHeap       =>     ntdll.NtAllocateVirtualMemory   
    ntdll.NtAllocateVirtualMemory     =>     ntdll.KiFastSystemCall   
    ntdll.KiFastSystemCall     =>     sysenter指令     (0F34)   
    
    (注意kernel32中实际没有HeapAlloc的入口地址,而是直接映射到ntdll.RtlAllocateHeap的入口)

 

 

 

 

 

弄清楚这个问题首先要了解VirtualCopy和VirtualAlloc这2个函数的实现和目的,以及wince下动态虚拟内存映射和静态虚拟内存映射。
先说VirtualAlloc 和VirtualCopy
VirtualAlloc 首先会从我们的虚拟地址空间中申请(或者说预留)一块虚拟空间,准备接下来要用它。注意此时,可用的物理内存并没有减少,只是虚拟地址少了一块可用的区域。
真正把这块之前reserved的虚拟空间映射到物理的内存区域就是由VirtualCopy来干的,此时,MMU的页表就会增加一个entry,来表示物理--虚拟的映射关系。

 

 

在没有C运行库可用的时候,要用VirtualAlloc来申请内存。

另外,VirtualAlloc还可对申请的内存有许多的控制。

1、可以保留地址空间但不是及分配内存,在需要的时候再分配;

2、可以声明分配的内存不使用分页;

3、可以指定内存在尽可能高的地址上分配;

4、指定内存的读、写、执行属性和内存保护;

5、禁止所分配内存被高速缓存。

等。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1. 背景

最近,我在把 PC 上的扫描仪驱动源码移植到 Window CE 系统中,遇到了内存分配的问题。我在 CE 进程中,要一次性分配高达 40M 的内存大小时就提示失败,说内存不足,可是内存是 512M 的肯定足够。本来对内存分配不甚了解,苦苦折腾,总算解决。再此做个 Windows CE 的内存分配的总结。一来是我温故知新,二来供他人参考之用。

2. WCE 内存简介

在进入主题之前,我想有必要简要了解下 WCE 的内存(详细了解可以参看有关书籍)。

2.1 WCE 内存结构

Windows CE 是一个保护模式的操作系统,每个进程的地址空间都是相对独立的,因此程序的访问只能使用虚拟内存。 Windows CE 对整个系统实现了一个线性的 32 位( 2 32 ,即 4GB )的虚拟地址空间。

Windows CE.net 只能管理 512MB 的物理内存和 4GB 大小的虚拟地址空间。这 4GB 的虚拟地址空间被分为 2 个的 2GB 的区域,一个是 2GB 的内核空间(程序无法访问,以保证系统的安全),一个是 2GB 的用户空间。用户空间又被划分为 64 32MB 的槽, Windows CE 系统最多同时可以运行 32 个进程,每个进程都有它自己受保护的、 32MB 的地址空间(而 Windows XP 拥有 2GB 的私有地址空间),同时也受到了 32MB 空间大小的限制。

2.2 、堆和栈

       堆是一段连续的相对较大的虚拟地址空间。堆具有以下特点:

◆堆是向高地址扩展的数据结构

◆一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收 。在堆中分别内存是以字节为单位。如 malloc new 等。

◆默认堆

当运用程序启动时,内核在进程地址所在空间中为进程分配了一个 192K 大小的虚拟地址空间作为默认堆使用。但是并不立刻提交物理内存。如果在运行当中 192KB 不能满足需求,那么内核会在进程地址空间中重新查找一个足够大小的空闲的地址空间,然后复制原来堆的数据,最后释放原来的堆所占的地址空间。这是因为默认的堆的高地址处还有栈,所以必须重新分配一个。

◆优点

堆的优点是能在一定范围内减少内存碎片。

◆缺点

Windows CE.NET 的堆有明显的缺点,不同于其它 Windows 操作系统下的堆管理,在 Windows CE.NET 的堆中创建的内存块不能够移动,多次创建内存块、释放内存块会产生内存碎块,这样的话当需要分配一个大一点的连续的内存块时,本来空闲的内存块加起来足够用,但是这些内存块是分隔的,不符合要求。像 Windows 2000 98 的内核会频繁的移动分散的正使用的内存块,使它们聚集在一起。这也是为什么有时需要句柄而不用指针的原因。由于 Windows CE.NET 的堆的缺点,开发者如果要频繁的在堆中创建、释放内存块的话,最好自己创建一个单独的堆,而不用默认的堆。

栈具有如下特点:

◆栈是向低地址扩展的数据结构

◆栈由编译器自动分配释放 ,如一个函数的参数和局部变量都在栈上。

◆栈也是一段连续的虚拟地址空间,和堆相比空间要小的多,它是专为函数使用的。

◆修改栈大小

可以在编译器设置修改栈的大小,或者在创建线程时指定栈的大小。如果采用在编译链接时修改大小,那么所有栈的大小都会改变。

2.4 内存映射文件

内存映射文件能保留一个虚拟地址空间,并提交物理内存。内存映射文件常用于:

◆映射大容量文件

运用程序不必在访问文件之前申请很大的内存作为读取文件的缓存区。

◆进程间通信

也是进程通信间的主要手段,其他进程间的通信机制都是基于内存映射文件来完成的。

◆申请大容量的虚拟地址空间

       内存映射文件在全局地址空间内( 0x420000 0x7FFFFFFF )分配。

2.5 申请效率的比较:

◆堆是由 malloc new 等分配的内存,一般速度比较慢,而且容易产生内存碎片 , 不过用起来最方便。

◆栈由系统自动分配,速度较快。但程序员是无法控制的。

◆在 WINDOWS 下,最好的方式是用 VirtualAlloc 分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

3 、比较分配内存的各种方法

看看这里: http://msdn.microsoft.com/en-us/library/aa366533(VS.85).aspx

Comparing Memory Allocation Methods

The following is a brief comparison of the various memory allocation methods:

Although the GlobalAlloc , LocalAlloc , and HeapAlloc functions ultimately allocate memory from the same heap, each provides a slightly different set of functionality. For example, HeapAlloc can be instructed to raise an exception if memory could not be allocated, a capability not available with LocalAlloc . LocalAlloc supports allocation of handles which permit the underlying memory to be moved by a reallocation without changing the handle value, a capability not available with HeapAlloc .

Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc have greater overhead than HeapAlloc .

Because the different heap allocators provide distinctive functionality by using different mechanisms, you must free memory with the correct function. For example, memory allocated with HeapAlloc must be freed with HeapFree and not LocalFree or GlobalFree . Memory allocated with GlobalAlloc or LocalAlloc must be queried, validated, and released with the corresponding global or local function.

The VirtualAlloc function allows you to specify additional options for memory allocation. However, its allocations use a page granularity, so using VirtualAlloc can result in higher memory usage.

The malloc function has the disadvantage of being run-time dependent. The new operator has the disadvantage of being compiler dependent and language dependent.

The CoTaskMemAlloc function has the advantage of working well in either C, C++, or Visual Basic. It is also the only way to share memory in a COM-based application, since MIDL uses CoTaskMemAlloc and CoTaskMemFree to marshal memory.

4 WCE 中申请大容量内存

WCE 运用程序的地址空间局限于 32MB 所引起的另外一个问题是如何分配大类型内存块。那么如何才能突破 32M 限制呢?

看看我的测试程序,用了不同的分配内存的方法来分配最大的内存。点击“ Alocates Memory ”开始分配,如图 4-1 可以看出,除了用内存映射外,其他的都无法突破 32M ,哪怕 VirtualAlloc 也不行。如果多次“ Alocates Memory ”你会发现内存映射文件能分配到的内内存越来越小,这我还没搞明白是什么原因,可能是内存碎片引起的。 HeapAlloc,GlobalAlloc,LocalAlloc,VirtualAlloc,malloc,new的异同

4-1 WCE 中各种方法分配最大内存

5 、总结

       写了好久,感觉写得好乱…… ^_^ 。但不管了,先发表,以后慢慢修改。如有错误,恳请赐教!