何时在 C #/. NET 中使用指针?

我知道 C # 让程序员能够在不安全的环境中访问和使用指针。但什么时候需要这样做呢?

在什么情况下,使用指针变得不可避免?

只是因为性能原因吗?

还有,为什么 C # 通过不安全的上下文公开这个功能,并从中移除所有的托管优势?从理论上讲,是否有可能在不失去托管环境的任何优势的情况下使用指针?

26356 次浏览

指针与受管理的、垃圾收集的环境之间存在内在的矛盾。
一旦你开始扰乱原始指针 GC 就不知道发生了什么。

具体来说,它不知道对象是否可以访问,因为它不知道指针在哪里。
它也不能在内存中移动对象,因为那样会破坏指针。

所有这些问题都可以通过 GC 跟踪的指针来解决; 这就是引用。

您应该只在混乱的高级互操作场景中使用指针,或者用于高度复杂的优化。
If you have to ask, you probably shouldn't.

在 C # 中显式使用指针的最常见原因:

  • 执行对性能非常敏感的低级工作(如字符串操作) ,
  • 与非托管 API 接口。

与指针相关的语法从 C # 中删除的原因(根据我的知识和观点,Jon Skeet 会回答更好的 B -)是因为在大多数情况下它是多余的。

从语言设计的角度来看,一旦通过垃圾收集器管理内存,就必须对指针可以做什么和不可以做什么进行严格的限制。例如,使用指针指向对象的中间可能会给 GC 带来严重的问题。因此,一旦限制到位,您就可以省略额外的语法并以“自动”引用结束。

此外,C/C + + 中的超仁慈方法也是常见的错误来源。在大多数情况下,微性能根本不重要,最好提供更严格的规则并限制开发人员,以减少很难发现的 bug。因此,对于常见的业务应用程序,所谓的“管理”环境包括。NET 和 Java 比那些自以为能够对抗赤裸裸的机器的语言更适合。

GC 可以移动引用; 使用不安全对象可以使对象处于 GC 的控制之外,从而避免这种情况。“修正”固定一个对象,但是让 GC 管理内存。

根据定义,如果您有一个指向对象地址的指针,而 GC 移动了它,那么您的指针就不再有效。

至于为什么需要指针: 主要原因是使用非托管 DLL,例如用 C + + 编写的 DLL

还要注意,当您钉住变量并使用指针时,您更容易受到堆碎片的影响。


剪辑

您已经触及了托管代码与非托管代码的核心问题... ... 如何释放内存?

您可以像您描述的那样混合代码以提高性能,只是不能使用指针跨越托管/非托管边界(即不能在“不安全”上下文之外使用指针)。

As for how they get cleaned... You have to manage your own memory; objects that your pointers point to were created/allocated (usually within the C++ DLL) using (hopefully) CoTaskMemAlloc(), and you have to release that memory in the same manner, calling CoTaskMemFree(), or you'll have a memory leak. Note that only memory allocated with CoTaskMemAlloc() can be freed with CoTaskMemFree().

另一种方法是从本机 C + + DLL 中公开一个方法,该方法接受一个指针并释放它... ... 这让 DLL 决定如何释放内存,如果它使用其他方法来分配内存,这种方法效果最好。您使用的大多数本机 dls 都是无法修改的第三方 dls,它们通常不具有(我见过的)这样的函数来调用。

一个释放内存的例子,取自 给你:

string[] array = new string[2];
array[0] = "hello";
array[1] = "world";
IntPtr ptr = test(array);
string result = Marshal.PtrToStringAuto(ptr);
Marshal.FreeCoTaskMem(ptr);
System.Console.WriteLine(result);


更多阅读材料:

C # 释放 IntPtr 引用的内存 下面的第二个答案解释了不同的分配/释放方法

如何在 C # 中释放 IntPtr? 增强了以分配内存的相同方式释放内存的需要

Http://msdn.microsoft.com/en-us/library/aa366533%28vs.85%29.aspx 关于分配和释放内存的各种方法的 MSDN 官方文档。

简而言之... 您需要知道内存是如何分配的,以便释放它。


Edit 如果我正确理解了您的问题,简短的回答是肯定的,您可以将数据交给非托管指针,在不安全上下文中使用它,并在退出不安全上下文后使数据可用。

关键是您必须使用 fixed块固定正在引用的托管对象。这可以防止在 unsafe块中引用的内存被 GC 移动。这里涉及到许多微妙的地方,例如,你不能重新分配一个在固定块中初始化的指针... ... 如果你真的想管理自己的代码,你应该读一读不安全的和固定的语句。

尽管如此,管理您自己的对象和以您所描述的方式使用指针的好处可能不会像您想象的那样为您带来很大的性能提升。原因:

  1. C # 是非常优化和非常快的
  2. 您的指针代码仍然作为 IL 生成,必须进行抖动(此时需要进行进一步的优化)
  3. 你没有关闭垃圾收集器... 你只是把你正在处理的对象保留在 GC 的范围之外。因此,每隔100毫秒左右,GC still就会中断您的代码,并为托管代码中的所有其他变量执行其函数。

HTH,
詹姆斯

什么时候需要这样做? 在什么情况下使用指针是不可避免的?

当受管理的安全解决方案的净成本不可接受,但不安全解决方案的净成本可以接受时。您可以通过从总成本中减去总收益来确定净成本或净收益。不安全解决方案的好处是“不浪费时间进行不必要的运行时检查以确保正确性”; 成本是(1)必须编写即使关闭托管安全系统也是安全的代码; (2)必须处理可能降低垃圾收集器效率的问题,因为它不能在有非托管指针的内存中移动。

或者,如果您是编写编组层的人员。

只是因为性能原因吗?

在托管语言中以性能以外的原因使用指针似乎有悖常理。

在绝大多数情况下,可以使用 Marshall 类中的方法处理与非托管代码的交互。(在少数情况下,可能很难或不可能使用编组装置来解决互操作问题,但我不知道有这样的情况。)

Of course, as I said, if you are the person writing the Marshal class then obviously you don't get to use the marshalling layer to solve your problem. In that case you'd need to implement it using pointers.

Why does C# expose this functionality through an unsafe context, and remove all of the managed advantages from it?

这些被管理的优势伴随着性能成本。例如,每次您询问一个数组的第十个元素时,运行时都需要检查是否有第十个元素,如果没有,则抛出异常。使用消除运行时成本的指针。

相应的开发人员成本是,如果执行错误,那么就需要处理内存损坏错误,这些错误会格式化硬盘,并在一小时后导致进程崩溃,而不是在错误发生时处理一个干净的异常。

从理论上讲,使用指针而不失去托管环境的任何优势是否可能?

我猜你说的“优势”是指垃圾收集、输入安全和参照完整性等优势。因此,你的问题本质上是“在理论上是否可能关闭安全系统,但仍然获得安全系统开启的好处?”不,显然不是。如果你关闭了安全系统,因为你不喜欢它是多么昂贵,那么你就不会得到它的好处!

假设你想在两个应用程序之间使用 IPC (共享内存)进行通信,那么你可以将数据封送到内存,并通过 Windows 消息传递或其他方式将这个数据指针传递给另一个应用程序。在接收应用程序时,您可以取回数据。

对于从以下位置传输数据也很有用。NET 到遗留的 VB6应用程序,其中您将封送数据到内存,传递指针到 VB6应用程序使用 win 消息,使用 VB6复制内存()获取数据从管理内存空间到 VB6应用程序非管理内存空间。.