nativememory.alloc 是 .net 5+ 轻量级非托管内存分配接口,返回 intptr,需手动 nativememory.free 释放,不触发 gc;推荐 alloczeroed 零初始化,避免敏感数据残留,性能优于 alloc + 手动清零。

NativeMemory.Alloc 分配非托管内存的基本用法
NativeMemory.Alloc 是 .NET 5+ 提供的轻量级非托管内存分配接口,替代部分 Marshal.AllocHGlobal 场景,特点是无句柄、无 GC 跟踪、不触发 Finalizer,适合短生命周期高性能缓冲区。
基本调用只需传入字节数:
IntPtr ptr = NativeMemory.Alloc(1024); // 分配 1KB
注意它返回的是 IntPtr,不是 void*,但可直接用于 Span<byte>.DangerousCreate</byte> 或 Unsafe.AsPointer。
- 必须手动调用
NativeMemory.Free(ptr)释放,否则内存泄漏 - 不保证内存清零(类似
malloc),如需初始化得自己填 0 或用NativeMemory.AllocZeroed - 分配失败时抛出
OutOfMemoryException,不返回 NULL
NativeMemory.AllocZeroed 和初始化控制
如果需要零初始化内存(比如避免敏感数据残留或跳过显式 Array.Clear),优先用 NativeMemory.AllocZeroed:
IntPtr ptr = NativeMemory.AllocZeroed(4096); // 分配并清零 4KB
它底层调用的是 VirtualAlloc(windows)或 mmap(MAP_ANONYMOUS|MAP_PRIVATE)(unix),语义上等价于 calloc。
-
AllocZeroed比Alloc+ 手动Unsafe.InitBlock更高效,系统级零页优化可能生效 - 不要在循环中反复
AllocZeroed小块内存——系统调用开销大,应复用或预分配池 - 没有“只清前 N 字节”的变体,清零范围严格等于请求大小
与 Marshal.AllocHGlobal 的关键区别
很多人下意识用 Marshal.AllocHGlobal,但它走的是 COM 堆管理器(尤其在 Windows 上),有额外开销和不同行为:
-
Marshal.AllocHGlobal可能返回大于请求的对齐地址(如 8/16 字节),NativeMemory.Alloc保证按sizeof(void*)对齐(通常 8 字节) -
Marshal.AllocHGlobal在低内存时可能抛COMException,而NativeMemory.Alloc统一抛OutOfMemoryException -
Marshal.AllocHGlobal分配的内存可被GCHandle引用,NativeMemory分配的完全脱离 GC 系统 - 跨平台一致性:linux/macos 上
Marshal.AllocHGlobal实际调用malloc,但行为不如NativeMemory明确可控
释放时机与常见崩溃点
最常踩的坑是释放逻辑错位——尤其是异步或异常路径下忘记 Free,或重复 Free 同一个 IntPtr:
try { IntPtr ptr = NativeMemory.Alloc(1024); // ... use } finally { NativeMemory.Free(ptr); // 必须确保执行,ptr 必须是作用域内有效变量 }
- 不能把
ptr存到类字段里再异步释放——没生命周期管理,极易悬垂指针 - 不要对同一
ptr调用两次Free,会触发AccessViolationException或静默损坏 - 没配
using语法糖,所以推荐封装成Struct+IDisposable(如MemoryOwner<byte></byte>风格),但要注意Dispose不可重入 - 调试时可用
NativeMemory.GetAllocatedSize(ptr)辅助验证(仅 Debug 构建有效)
真正难处理的是跨线程传递指针——NativeMemory 不做线程安全保证,所有分配/释放必须由同一逻辑上下文控制。