在C#中应该使用指针(不安全的代码)吗?

48

你应该在C#代码中使用指针吗?有哪些好处?这是由微软推荐的吗?


指针的使用通常与p/invoke相关。请参阅此文章:http://www.c-sharpcorner.com/UploadFile/pcurnow/usingpointers10022007082330AM/usingpointers.aspx - gsscoder
如果您正在为一个特定用途大量使用不安全的代码,将所有这些代码放在一个私有内部类中,并将其包含在一个安全的公共类中可能会有所帮助。我不喜欢让应用程序处理指针的想法。任何出现的模糊错误都可以追溯到这对类,而不是在应用程序的其他未知位置。它可以防止增加的复杂性泄漏到代码库的其他部分。 - marknuzz
11个回答

44

以下是来自 “The Man” 的话:

C# 中很少需要使用指针,但有一些情况需要使用它们。例如,以下情况需要在不安全的上下文中使用指针:

  • 处理磁盘上的现有结构
  • 涉及具有指针的结构的高级 COM 或平台调用方案
  • 对性能非常关键的代码

在其他情况下使用不安全的上下文是不鼓励的。

具体而言,不应尝试在 C# 中编写 C 代码时使用不安全的上下文。

注意:

使用不安全上下文编写的代码不能保证安全,因此只有在完全信任代码时才会执行它。换句话说,不安全的代码无法在不受信任的环境中执行。例如,您无法直接从互联网运行不安全的代码。

参考资料


2
通常情况下,当我在互联网上的示例中看到不安全的C#代码时,这是一种代码异味(code smell)。也就是说,有人试图在C#中编写C代码。此时,Managed.Interop命名空间已经非常广泛,您可以将IntPtr包装到单实例结构体中,因此unsafe关键字主要用于CLR专家编写的对性能敏感的代码。这不是在托管世界中保持指针旧技巧的途径。 - Andrew Rondeau

24
如果必须这样做,那么请按照以下方法对一个大的灰度图像(例如2000x2000像素)进行假彩色处理。首先使用GetPixel()SetPixel()编写“安全”的版本。如果运行良好,则继续进行。如果速度过慢,则可能需要获取组成图像的实际位(为了示例简单,请勿考虑颜色矩阵)。使用不安全代码并没有什么“不好”,但它会增加项目的复杂性,因此只有在必要时才应使用。

1
请注意,处理图像可能是使用不安全代码以提高性能的最常见用途。然而,我认为在转向unsafeLockBits之前,您应该仅使用LockBits作为中间步骤。 - TheLethalCoder
@TheLethalCoder:嗯,也许吧,如果你没有经验的话。一旦你做过这项工作,你就知道是否需要它了。这不是过早优化。 - Ed S.
1
我非常同意。 考虑将图像从一个平台转换到另一个平台,每个平台使用不同的类和格式来表示图像。 您可以复制数组,迭代以进行转换,然后将其复制到目标中,或者......您可以使用指针在一次传输中从源复制并转换到目标。 如果您知道自己在做什么,“安全”的方式效率极低。 - Daniel

14

我不记得曾经有这样的需求 - 但我没有做过太多的互操作。我认为这是最常见的应用程序:调用本地代码。 在极少数情况下,使用指针可以优化一些代码,但在我看来这种情况非常罕见。

如果需要参考文献,我认为自己在C#方面相当有经验,但如果我必须编写任何不安全的代码,我会查阅规范/书籍/MSDN进行指导。当然,会有很多人对不安全的代码感到满意,但对查询表达式等技术不太熟悉...


8
我经常进行图像处理,而 LockBits 为我节省了许多精力。但是,我从未在其他地方使用过不安全代码。 - Ed S.
我有时候会用它,但甚至与交互使用也很少。我们包装了16位TWAIN并且只需要使用IntPtr而不是不安全的代码。我想到唯一使用它的地方是为了位图操作,这样我就可以固定内存。 - Steven Behnke
2
我唯一使用它的时候也是用于图像处理。我必须反转图像的特定部分,如果不使用不安全代码,会有明显的延迟,但使用不安全代码后,处理时间少于1毫秒。 - Caleb Vear
4
使用LockBits进行图像处理和在遗传算法中进行一些位级别的操作是我唯一需要使用它的时候,但性能提升非常惊人! - Steven A. Lowe
对于图像处理,GetPixel和SetPixel方法非常慢。我还使用它进行快速位图编辑,包括一个遗传算法,这将是我的EvoLisa项目的自己版本;) 除此之外,我只记得使用调用Interop函数来获取*.ico文件中的所有图标,因为.net版本不支持。 - Carra

6

我认为主要问题是:

  • 不安全的代码是不可验证的。这意味着该代码只能由完全受信任的上下文中的用户运行,因此如果您需要让用户从任何不完全受信任的地方运行代码(例如未配置为如此的网络共享),则会出现问题。
  • 缺乏可验证性也意味着您可能会在程序中搞砸内存。您可能会将整个类别的错误带回应用程序 - 缓冲区溢出,悬空指针等等。更不用说当您的指针变得奇怪时,可能会无意中损坏内存中的数据结构。
  • 如果您希望您的不安全代码访问托管对象,则需要将它们“固定”。这意味着GC不允许在内存中移动您的对象,因此托管堆可以变得分散。这对性能有影响;因此,始终重要的是确定任何潜在的性能增益是否被此问题所抵消。
  • 编写不安全代码的程序员难以理解。他们可能更容易使用一些“自由”不安全代码来使自己陷入困境。
  • 您将能够编写非类型安全的代码;这实际上淘汰了很多良好且易于理解的托管语言的优势。现在,您可能会遇到可怕的类型安全问题。为什么要向后退呢?
  • 它使您的代码更加丑陋。
我相信列表中还可以添加更多的内容;总体而言,正如其他人所说-除非必须,否则避免使用。例如,通过p/invoke调用未管理的方法需要一些特殊的指针操作。即使这样,大多数情况下,编组器也会防止其需要。
“那个人”也说除非必要,否则基本上应该避免使用。
顺便说一句,这里有一个关于固定 在MSDN上的不错文章

请记住,在完全信任的情况下,验证器是被禁用的,这意味着您的“安全性”实际上取决于编译器是否正确执行其工作。基本上,您可以创建执行不安全操作的 IL。 - Dinis Cruz

4

我使用了不安全的代码来模拟用户身份,以允许服务访问网络共享。只要你知道自己在做什么,这并不是问题。


3

不安全的代码是.NET CLR的一个完全支持的功能。它能够提高性能并与二进制代码兼容。运行时是一个沙盒,可以防止程序崩溃,但这也带来了一些成本。在处理大块内存数据(例如图像处理)等需要进行极端密集操作的情况下,放弃正常的运行时安全措施会更快。

尽管如此,我认为大多数人都会说“不要这样做”。在正常开发过程中,绝大多数.NET开发人员不会遇到只有使用不安全代码才能解决的问题。


2

如果您需要,可以使用它们;通常情况下,这将是在处理一些棘手的交互操作场景时(例如当我为.NET 1.0编写DPAPI托管包装器时就需要它们),但非常偶尔地,可能会通过使用stackalloc或类似方法来提高性能(在分析后!)。

正如微软所建议的那样,因为他们是C#的设计者,并且他们决定在其中添加编写unsafe代码的功能。从关键字的选择以及用该关键字界定编写它的方法/类的要求可以看出,它并不是旨在成为默认实现选择。


2

像BitConverter一样的重新解释类型不提供。

具体来说,将一个无符号整数转换为int用于哈希函数,其中您关心的仅是位。

在需要将结构体视为已知长度的byte*的情况下,使用一些有用的、经过良好推理的C或C++惯用函数,这对于哈希非常有用。

通过对它们的数组执行极快的二进制序列化(非常特定的)内存结构体,但老实说,最好还是转到C++/CLI。

必须说,在许多情况下,需要指针的任务通常可以通过在C++/CLI中完成并将其导入到您的C#项目作为dll来更好地解决。这不会改变代码是否“安全”,但它使得操作基于指针的结构的一组有用的功能更易于访问。如果您真的想要,它还允许您玩弄通用类型或枚举。

大多数开发人员需要这样做的可能性确实很小。但在需要时非常有用...


1
当然这并不是“推荐”的,这就是为什么它被标注为“不安全”的原因。但不要被吓到。尽管如此,这应该让你仔细检查你的代码。也许有一种受管理的方法来完成它?

0

使用不安全代码就像忘记了 .Net Framework 的好处一样,我曾经用它们创建过老式的结构,比如堆栈等,但那只是为了学校,现在我没有必要使用它们。


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