Windows|说说LocalAlloc和GlobalAlloc的区别


Windows|说说LocalAlloc和GlobalAlloc的区别

他们俩的区别 , 在16位Windows的年代 , 还是挺大的 。
在16位Windows系统中 , 内存是通过段选择子(selectors)来访问的 , 每个段最大可以寻址64KB 。 其中 , 有一个默认的段称之为”数据段” , 一个近指针(near pointers)操作会以距离数据段的偏移来进行操作 。 举个例子 , 如果你有一个近指针p , 它的值是0x1234 , 然后数据段的值为0x012F , 则当你写入数据到*p时 , 你即将访问的地址为012F:1234 。 (当你声明一个指针的时候 , 默认情况下它是近指针 。 如果你想使用一个远指针 , 则需要在声明的时候添加FAR)
重要的是 , 近指针总是相对于段的 , 通常这个段就是上面所说的数据段 。
GlobalAlloc这个函数会分配一个段选择子 , 进而通过它来访问你请求的内存地址 。 (如果你要求访问的地址空间大于64KB , 则将会发生不同的故事 , 当然 , 这不是今天的主题)
你可以使用这个段选择子来声明一个远指针 。 一个远指针包括一个段选择子和一个近指针 。 (请记住 , 一个近指针是相对一个段选择子的偏移 , 当你将一个近指针和一个合适的段选择子组合在一起的时候 , 你就得到了一个远指针了)
每个程序的实例和它依赖的DLL , 都会拥有属于它自己的数据段 , 也就是我们常说的实例句柄 , 关于这个实例句柄 , 你应该记得 , 我在之前的一篇文章中专门讨论过 。 默认的数据段会作为一个应用程序实例的句柄 , 而在一个DLL中的默认数据段会作为这个DLL模块的句柄 。 因此 , 如果你有一个近指针p , 然后在一个应用程序中通过*p访问数据 , 则它会相对程序的实例句柄来作为基础地址 。 如果你从一个DLL中进行地址访问 , 则它会基于DLL模块的句柄作为基础地址 。
【Windows|说说LocalAlloc和GlobalAlloc的区别】通过调用LocalInit函数 , 被默认数据段所引用的内存会转变为一个”本地堆”(Local Heap) 。 初始化这个本地堆通常是一个程序或者DLL在启动加载后需要做的第一件事(对于DLL来说 , 通常这是它需要做的唯一一件事) 。 当完成本地堆的初始化之后 , 你就可以使用LocalAlloc来分配内存了 。 LocalAlloc函数会返回一个相对于默认数据段的近指针 , 所以当你从一个应用程序中调用LocalAlloc时 , 它会从程序的地址空间以程序实例句柄作为基础地址进行分配 。 如果你在一个DLL中调用它 , 则会从DLL中的模块句柄开始进行地址分配 。
聪明的你一定发现了 , 我们可以使用LocalAlloc来分配地址而不是实例句柄 。 我们需要做的 , 只是将默认的段修改为其他通过GlobalAlloc分配的内存地址 , 然后再次使用LocalAlloc , 最后将将段还原为之前的默认段 。 这可以实现以非默认段为基础地址的地址访问 , 这种常见非常少见 , 但是如果你足够小心和谨慎 , 一般不会出现大的问题 。
所以 , 从上面的描述 , 我们可以看到 , 在16位Windows上 , LocalAlloc和GlobalAlloc有着完全不同的行为 。 LocalAlloc会返回一个近指针 , 而GlobalAlloc会返回一个段选择子 。
对于跨模块传输指针的场景 , 这个指针必须是一个远指针 , 因为每个模块都有一个不同的默认段 。 如果你希望将一块内存的访问控制权转移到另一个模块 , 则你需要使用GlobalAlloc , 因为接收者可以使用GlobalFree来释放它(有人可能有些迷糊了 , 接收者并不能使用LocalFree , 因为LocalFree适用于本地堆 , 它释放的将会是接收者自己的堆 , 而不是发送者的) 。
到了Win32的年代 , 本地堆和全局堆之间的区别已经很小了 。 如果你调用一个从16位版本Windows继承而来的函数 , 并使用它来传递内存控制权 , 则它会使用一个HGLOBAL的形式 。 剪贴板相关的API就是这种场景的一个经典例子 。 如果你将一块数据放置到剪贴板 , 它必须使用HGLBAL的形式进行内存分配 , 因为这个时候 , 你是将内存数据传输到剪贴板 , 然后剪贴板会在不需要访问此内存的时候 , 调用GlbalFree来进行内存释放 。 而通过STGMEDIUM来进行数据传输 , 也是基于相同的原因使用HGLOBAL 。
即使是在Win32中 , 你也必须小心谨慎的区分好本地堆和全局堆 。 从其中一个堆中分配的内存 , 不能被另一个堆释放函数所释放 。 这两个函数在功能上的区别已经基本没有了 , 语法上也接近一致 。 由于迁移到了Win32的平坦内存模型中 , 之前的近指针和远指针也不再使用到了 。 但是本地堆和全局堆两个的函数接口还是要看做是两套不同的内存访问接口 。