C# 本机内存分配方法 C#如何使用NativeMemory分配非托管内存

1次阅读

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

C# 本机内存分配方法 C#如何使用NativeMemory分配非托管内存

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

它底层调用的是 VirtualAllocwindows)或 mmap(MAP_ANONYMOUS|MAP_PRIVATE)unix),语义上等价于 calloc

  • AllocZeroedAlloc + 手动 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/macosMarshal.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 不做线程安全保证,所有分配/释放必须由同一逻辑上下文控制。

text=ZqhQzanResources