什么时候在C#/ .NET中使用指针?

Joa*_*nge 70 .net c# performance pointers unsafe

我知道C#为程序员提供了访问的能力,在不安全的上下文中使用指针.但什么时候需要呢?

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

是出于性能原因吗?

另外,为什么C#会通过不安全的上下文公开此功能,并从中删除所有托管优势?从理论上讲,是否可以使用指针而不会失去托管环境的任何优势?

Eri*_*ert 87

什么时候需要?在什么情况下使用指针变得不可避免?

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

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

是出于性能原因吗?

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

在绝大多数情况下,您可以使用Marshal类中的方法来处理与非托管代码的互操作.(在某些情况下,很难或不可能使用编组设备来解决互操作问题,但我不知道.)

当然,正如我所说,如果你是编写元帅类的人,那么显然你不会使用编组层来解决你的问题.在这种情况下,您需要使用指针实现它.

为什么C#通过不安全的上下文公开此功能,并从中删除所有托管优势?

这些管理优势伴随着性能成本.例如,每次向数组请求第十个元素时,运行时都需要检查是否存在第十个元素,如果没有则抛出异常.使用指针消除运行时成本.

相应的开发人员成本是,如果你做错了,那么你就可以处理内存损坏错误,这些错误会使你的硬盘格式化并在一小时后崩溃你的进程,而不是在错误点处理一个漂亮的干净异常.

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

通过"优势",我认为你的意思是垃圾收集,类型安全和参照完整性等优点.因此,你的问题基本上是"理论上可以关闭安全系统,但仍然可以获得安全系统开启的好处吗?" 不,显然不是.如果你关闭那个安全系统,因为你不喜欢它有多贵,那么你就无法获得它的好处!

  • @Joan:每个引用实际上都指*有效*或*null*.指针没有该属性; 指针可以指的是根本没有任何好处的内存.但托管引用具有该属性; 如果你有一个字符串的引用,那就是*always*null或者是一个有效的字符串; 保证不会出现对非有效字符串的非空引用的情况. (8认同)
  • @masoudkeshavarz:不.使用托管指针,不可能伪造指向任意内存的指针.对于不安全代码中的非托管指针,我们只是说它们被称为"非托管"和"不安全"**,原因**.您可以使用不安全代码中的非托管指针执行任何您喜欢的操作,包括破坏.NET运行时数据结构. (2认同)
  • 天啊,我花了一个小时寻找一个明确的、没有废话的答案,这真是太神奇了。谢谢你! (2认同)

SLa*_*aks 18

指针是管理的垃圾收集环境的固有矛盾.
一旦你开始搞乱原始指针,GC就不知道发生了什么.

具体来说,它无法判断对象是否可访问,因为它不知道指针的位置.
它也无法在内存中移动对象,因为这会破坏你的指针.

所有这些都可以通过GC跟踪指针来解决; 这是什么参考.

您应该只在凌乱的高级互操作方案中使用指针或进行高度复杂的优化.
如果你不得不问,你可能不应该.

  • +1代表*如果你不得不问,你可能不应该*.优秀的建议:-) (7认同)
  • @SLaks:相当可观.顺便说一句,您的假设GC跟踪指针确实存在于其他.NET语言中(尽管有一些限制 - 它只能是一个自动变量),它支持算术:[`interior_ptr`](http://msdn.microsoft .COM/EN-US /库/ y0fh545k.aspx) (3认同)

Jam*_*ing 5

GC可以移动参考; 使用unsafe将对象保持在GC的控制之外,并避免这种情况."固定"固定一个对象,但让GC管理内存.

根据定义,如果您有一个指向对象地址的指针,并且GC移动它,那么您的指针将不再有效.

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

另请注意,当您固定变量并使用指针时,您更容易受到堆碎片的影响.


编辑

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

您可以在描述时混合代码以获得性能,您只是不能使用指针跨越托管/非托管边界(即,您不能在"不安全"上下文之外使用指针).

至于他们如何清理......你必须管理自己的记忆; 使用(希望)创建/分配指针指向的对象(通常在C++ DLL中)CoTaskMemAlloc(),并且必须以相同的方式释放该内存,调用CoTaskMemFree(),否则您将发生内存泄漏.请注意,只能CoTaskMemAlloc()释放分配的内存CoTaskMemFree().

另一种方法是从你的本机C++ dll中公开一个方法,该方法接受一个指针并释放它...这让DLL决定如何释放内存,如果它使用其他方法来分配内存,那么它最有效.您使用的大多数本机dll是您无法修改的第三方dll,并且它们通常没有(我已经看到)这样的函数可以调用.

这里取出一个释放内存的例子:

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);
Run Code Online (Sandbox Code Playgroud)


更多阅读材料:

IntPtr引用的C#deallocate内存 第二个答案说明了不同的分配/释放方法

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

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

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


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

关键是您必须使用fixed块固定您引用的托管对象.这可以防止在unsafe块中时GC移动您引用的内存.这里涉及许多细微​​之处,例如,你无法重新分配在固定块中初始化的指针......如果你真的开始管理你自己的代码,你应该阅读不安全和固定的语句.

总而言之,管理您自己的对象并以您描述的方式使用指针的好处可能不会像您想象的那样为您带来性能提升.不理由的原因:

  1. C#非常优化且非常快
  2. 您的指针代码仍然生成IL,必须进行jitted(此时进一步优化)
  3. 你没有关闭垃圾收集器......你只是将你正在使用的对象保留在GC的范围之外.因此,每隔100ms左右,GC 仍会中断您的代码并为托管代码中的所有其他变量执行其功能.

HTH,
詹姆斯

  • @Joan:好的.但是*你*负责确保清理所有内容,没有可移动内存的杂散指针,等等.如果您想要关闭安全系统的好处,那么您必须承担安全系统通常为您做的事情的成本. (2认同)