Marshal.AllocHGlobal(0) - 为什么这不是 return IntPtr.Zero

Marshal.AllocHGlobal(0) - Why does this not return IntPtr.Zero

只是想了解这是否有意义以及其中的意义所在。

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

但为什么 Marshal.AllocHGlobal(0) 实际上是 return 一个 IntPtr 而不是 而不是 IntPtr.Zero?我是否应该在使用完 0 字节后释放分配的 0 字节?

我看不出这个实现背后的逻辑,谁能解释一下?

对于某些用例,对 AllocHGlobal 的两次不同调用永远不会 return 相同 IntPtr 值(缺少任何FreeHGlobal 个调用),即使其中两个调用碰巧指定了一个有点无意义的大小值。

归根结底,您调用此函数可能是出于与期望与 "global" 堆一起工作的非托管代码的互操作目的。并且 GlobalAlloc 长期以来一直被声明接受 0 值,并且该函数实际上总是执行一些分配(如果它成功)。

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

Marshal.AllocHGlobalWinBase.h.

内部调用 WinAPI 函数 LocalAlloc

至于为什么Marshal.AllocHGlobal(0)没有returnIntPtr.ZeroLocalAlloc 仅 returns 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 个字节 return 一个(有效的)内存地址?

documentation 表示 LocalAlloc 的 return 值:

If the function succeeds, the return value is a handle to the newly allocated memory object.

If the function fails, the return value is NULL.

现在,LocalAlloc 仅当‡ uBytes 为负数时才会失败;正值或零值都没有问题。

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

‡ 失败还有其他原因,例如内存不足。为简单起见,在此解释中将它们排除在外。


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

LocalAlloc的签名是这样的:

DECLSPEC_ALLOCATOR HLOCAL LocalAlloc(
  UINT   uFlags,
  SIZE_T uBytes
);

documentation 表示

if [uBytes] is zero and the uFlags parameter specifies LMEM_MOVEABLE, the function returns a handle to a memory object that is marked as discarded.

出于某种原因,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);
}

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

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

只分配一次内存并且return每次分配相同的地址

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