删除C#中的不安全指针

8

我知道在C#中使用/unsafe标记可以使用指针。在C/C++中,要删除指针,你需要分别使用free(pointer);delete pointer;。然而,如何在C#中实现相同的效果呢?

2个回答

32
这要看情况。你可以使用freedelete来释放通过mallocnew分配的内存。

但是

一般来说,如果你进行PInvoke调用,则指针应该是一个IntPtr

如果你使用fixed(或GCHandle)获取托管对象的指针,则该内存是从GC内存中分配的。

  • 对于GC的内存,在取消固定该内存(退出fixed块或释放GCHandle)时,GC将返回处理它
  • 对于通过.NET Marshal方法分配的内存,需要使用相应的Free方法
  • 对于从本地方法接收的内存,必须使用“正确”的本地方法来释放它。

以下是通过.NET接收固定内存的示例:

int[] arr = new int[5];

fixed (int* p = arr)
{
    // here arr is fixed in place and it won't be freed/moved by gc
}

// here arr is un-fixed and the GC will manage it

或者,几乎等效(但不太安全,因为解除固定是手动执行的)
GCHandle handle = GCHandle.Alloc(arr, GCHandleType.Pinned);

int* p2 = (int*)handle.AddrOfPinnedObject();

// here arr is fixed in place and it won't be freed/moved by gc

handle.Free();
// here arr is un-fixed and the GC will manage it

使用Marshal.AllocCoTaskMem从“本地”池中分配一些内存(通过COM对象通常使用的分配器),注意Marshal.AllocCoTaskMem调用Windows API的CoTaskMemAlloc,因此您可以使用Marshal.FreeCoTaskMem和Windows API的CoTaskMemFree来释放它:

// allocating space for 1000 chars
char* p3 = (char*)Marshal.AllocCoTaskMem(1000 * sizeof(char));

// here you can use p3

// and here you free it
Marshal.FreeCoTaskMem((IntPtr)p3);

或者使用 Marshal 支持的另一个分配器(这通常是 Windows API 使用的分配器):
// allocating space for 1000 chars
char* p4 = (char*)Marshal.AllocHGlobal(1000 * sizeof(char));

// here you can use p4

// and here you free it
Marshal.FreeHGlobal((IntPtr)p4);

假设你有一些本地代码,它可以访问并保存一些数据到内存中:
static extern IntPtr GetSomeMemoryFromSomeWinApi();

static extern void FreeSomeMemoryFromSomeWinApi(IntPtr ptr);

你可以这样使用:

IntPtr p5 = GetSomeMemoryFromSomeWinApi();

// here you have some memory received from some native API

// and here you free it
FreeSomeMemoryFromSomeWinApi(p5);

在这种情况下,是您的库必须提供Free方法,因为您不知道内存是如何分配的, 但有时您的库的文档会告诉您内存是通过特定的分配器进行分配的,因此您可以使用该类型的解除分配器来释放它,例如:

Marshal.FreeCoTaskMem(p5);

如果API是某个COM对象。 Marshal类甚至有BSTR的分配器(COM对象使用的Unicode字符串,它们的长度被预先添加)。
string str = "Hello";
char *bstr = (char*)Marshal.StringToBSTR(str);

Marshal.FreeBSTR((IntPtr)bstr);

它们有特殊处理,因为它们的“真实”起始地址类似于(bstr-2)(它们前面有一个带有长度的Int32)

问题在于,存在着像沙漠中的沙粒和天空中的星星一样多的分配器。除了.NET标准分配器(即new使用的分配器)之外,每个分配器都有相应的解除分配器。它们就像夫妻一样,不与其他人混在一起。

最后需要注意的是,如果您编写混合的.NET/本机C或C++代码,则必须公开某些C/C++方法,以调用它们的free/delete,因为它们的free/delete是它们的C/C++库的一部分,而不是操作系统的一部分。


这里的一切都很好,除了建议使用IntPtr作为PInvoke指针。使用不安全的struct *并允许编译器检查指针类型会更安全。IntPtr是一个漏洞,使VB和据说“安全”的代码可以通过将PInvoke参数视为IntPtr / void *来执行非常不安全的操作。 - David Jeske

0

在 .NET 6 中有一种新的功能,可以使用 C API 分配本地内存,即使用新的 NativeMemory。使用这种新方法,您可以轻松删除已分配的内存:

using System.Runtime.InteropServices;

unsafe
{
    byte* buffer = (byte*)NativeMemory.Alloc(100);

    NativeMemory.Free(buffer);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接