Marshal.AllocHGlobal(0)-为什么不返回IntPtr.Zero

时间:2018-08-29 08:53:06

标签: c# .net marshalling

只是试图了解这是否有意义以及意义在何处。

Marshal.AllocHGlobal(int cb)在非托管内存中分配指定数量的字节。

但是为什么Marshal.AllocHGlobal(0)实际上返回不是 IntPtr的{​​{1}}?使用完0个字节后,我应该释放分配的0个字节吗?

我看不到此实现背后的逻辑,有人可以解释吗?

2 个答案:

答案 0 :(得分:4)

1。如果分配了0个字节,为什么Marshal.AllocHGlobal不返回IntPtr.Zero

Marshal.AllocHGlobalWinBase.h内部调用WinAPI函数LocalAlloc

关于Marshal.AllocHGlobal(0)不返回IntPtr.Zero的原因: 如果分配期间失败,LocalAlloc仅返回NULL(等效于C#:IntPtr.Zero)。 也可以在source code中看到:

IntPtr pNewMem = Win32Native.LocalAlloc_NoSafeHandle(LMEM_FIXED, unchecked(numBytes));

if (pNewMem == IntPtr.Zero) {
    throw new OutOfMemoryException();
}
return pNewMem;

2。为什么分配0字节返回一个(有效的)内存地址?

documentation说说LocalAlloc的返回值:

  

如果函数成功,则返回值是新分配的内存对象的句柄。

     

如果函数失败,则返回值为NULL

现在,LocalAlloc 仅在‡uBytes为负的情况下失败;正值或零值都没有问题。

这意味着分配将始终成功‡,并且如果您尝试分配0个字节,您将始终收到有效的指针。

‡失败还有其他原因,例如内存不足。为简单起见,在此说明中将它们省略。


3。我应该释放Marshal.AllocHGlobal(0)分配的内存吗?

LocalAlloc的签名是这样:

DECLSPEC_ALLOCATOR HLOCAL LocalAlloc(
  UINT   uFlags,
  SIZE_T uBytes
);

documentation指出

  

如果[{uBytes]为零,并且uFlags参数指定LMEM_MOVEABLE,则该函数将返回标记为已废弃的内存对象的句柄。

出于某种原因,Marshal.AllocHGlobal(0)不会通过LMEM_MOVEABLE,而是通过LMEM_FIXED

文档缺少有关此特定案例的信息。运行测试(如下所示)表明实际上已分配了内存,并且您绝对需要释放内存,如下所示:

IntPtr zeroBytesPtr = Marshal.AllocHGlobal(0);

// Do stuff with the pointer.

Marshal.FreeHGlobal(zeroBytesPtr);

如果Marshal.AllocHGlobal传递了LMEM_MOVEABLE,则无需在任何地方释放指针。


对于测试:

while(true) {
    void* v = LocalAlloc(LMEM_FIXED, 0);
}

为循环的每次迭代分配内存,并且每次都返回一个新地址,而

while(true) {
    void* v = LocalAlloc(LMEM_MOVEABLE, 0);
}

仅分配一次内存,并每次返回相同地址

这表明为什么必须释放Marshal.AllocHGlobal分配的内存(因为它使用LMEM_FIXED),因为每次调用都会分配一个新的内存对象。

答案 1 :(得分:1)

在某些用例中,对AllocHGlobal的两个不同调用永远不会返回 same IntPtr值(不存在任何FreeHGlobal调用),甚至可能很重要如果有两个调用恰好指定了一个有点废话的大小值。

最终,您可能是为了与非托管代码(希望与“全局”堆一起使用)互操作而调用此函数。长期以来,GlobalAlloc都被接受为0值,并且该函数实际上总是执行一些分配(如果成功的话)。