C#关键字fixed/unsafe有什么用?

29

C# 关键字 fixed/unsafe 有什么用?

例如,C# fixed 关键字 (unsafe)

using System;

class Program
{
    unsafe static void Main()
    {
        fixed (char* value = "sam")
        {
            char* ptr = value;
            while (*ptr != '\0')
            {
                Console.WriteLine(*ptr);
                ++ptr;
            }
        }
    }
}

我为什么需要首先修复它?


请参见以下链接:https://dev59.com/MHVC5IYBdhLWcg3w4Vf6 或 https://dev59.com/9HVD5IYBdhLWcg3wHnoX 或 https://dev59.com/X3A75IYBdhLWcg3wqK__。 - Belial09
6
您需要修复指针以使其保持有效。但实际上,不安全代码的使用相对较少。我已经写了很多年C#,甚至都不记得不安全编码的各种规则。 - Jon Skeet
请查看此链接:http://msdn.microsoft.com/zh-cn/library/f58wzh21.aspx - w.b
@TimurAykutYıldırım 我不认为这是重复的 - 那个问题问的是什么时候,而这个问题也问了为什么。 - markmnl
请参阅:https://dev59.com/Hqnka4cB1Zd3GeqPHwtm - Rekshino
1
在链接的示例中找不到任何解释这个语句的答案:“使用 fixed 语句会有一些成本。因此,它只对在不安全代码中花费大量时间的操作有帮助”,这至少是含糊不清的,可能是错误的,取决于您如何理解第一部分。不清楚的是,当垃圾回收器重新定位分配的内存块时,CLR 是否会调整“非固定”指针(代价是一些时间损失),或者是否不会调整并且开始指向错误的位置(因此,'fixed' 关键字不是可选的,而是必须与指针一起使用--这是我的理解)。 - mins
3个回答

55

C#是一种被管理的语言,这意味着内存由系统自动管理,而非由你手动管理。如果在修改指针所指向的内存之前没有使用fixed,C#会将变量移动到另一个内存位置,因此你可能会修改其他内容!

fixed逻辑上固定了变量在内存中的位置,使其不会移动。

为什么C#会将变量在内存中移动?为了压缩内存,否则程序会占用更多可用的内存空间,如果不再使用的对象留下空洞,其他对象就无法适应(堆内存碎片化)。

我在设计用于资源限制设备的.NET库广泛使用中使用了fixed,以避免创建垃圾并将其复制到缓冲区中,并发现其他管理语言严重缺乏此功能。在编写管理语言的游戏时,垃圾收集通常是最大的瓶颈之一,因此不创建垃圾的能力非常有帮助!

参见我的问题在C#中将变量复制到缓冲区而不创建垃圾?,这是其中一个原因。


5
谢谢,但是抱歉能否详细说明一下。我似乎理解了这个想法,真正固定内存位置是为了提高性能。但我不太明白,如果您能详细说明一下,我会很感激! - Swab.Jat
2
固定内存并不能使性能具备使用指针修改内存的能力,在我的情况下,将几个变量复制到缓冲区中而不创建垃圾可以提高性能,特别是在垃圾回收时间成为问题时,详见我链接的问题。 - markmnl
谢谢马克。我看了一下。我想知道,通过不使用中间的byte[],而是通过固定内存位置进行复制,你获得了多少性能提升?这个提升是否显著? - Swab.Jat
如果您正在使用手机或像Ouya这样运行mono的设备,并且正在进行大量复制操作,那么当GC运行时,应用程序会冻结,但现在不会了,因为没有垃圾。 - markmnl
非常感谢您的输入。 - Swab.Jat

14

unsafe关键字在处理指针时是必需的。

fixed有两个用途:

  • 它允许您固定一个数组并获取指向数据的指针
  • 当在unsafe struct字段中使用时,它声明了一个“固定缓冲区” - 一种类型中保留的空间块,通过指针而不是常规字段来访问

以下是用于执行两个任意大小的byte[]之间的语义相等的代码示例...

    internal static unsafe  int GetHashCode(byte[] value)
    {
        unchecked
        {
            if (value == null) return -1;
            int len = value.Length;
            if (len == 0) return 0;
            int octects = len / 8, spare = len % 8;
            int acc = 728271210;
            fixed (byte* ptr8 = value)
            {
                long* ptr64 = (long*)ptr8;
                for (int i = 0; i < octects; i++)
                {
                    long val = ptr64[i];
                    int valHash = (((int)val) ^ ((int)(val >> 32)));
                    acc = (((acc << 5) + acc) ^ valHash);
                }
                int offset = len - spare;
                while(spare-- != 0)
                {
                    acc = (((acc << 5) + acc) ^ ptr8[offset++]);
                }
            }
            return acc;
        }            
    }

比如说,如果缓冲区有1000个项目,通过将它视为long的集合,我们现在只需要进行125次迭代,而不必逐个查看所有1000个项目 - 而且我们完全绕过了任何数组界限检查(这取决于JIT是否会删除它们,具体取决于您是否明显违反它们)。


谢谢Marc - "例如,缓冲区有1000个项目,通过将其视为一组长整数,我们现在只需要进行125次迭代,而不必逐个查看所有1000个项目" --- 我不确定我是否理解你的意思,但我猜想你是说这基本上是“性能考虑”。如果您有一个大的对象图 - 有时,如果您在迭代过程中固定其内存位置,它会更快?如果是这样,那么这是我迄今听到的唯一的“实际”用途(其他人只是重申“是的,在内存中修复它”)。 - Swab.Jat
2
@Swab.Jat 如果你看代码,你会发现它将每个项处理为 long 而不是 byte - 因此每次迭代都会哈希 8 个字节。是的,通常情况下 unsafe 是用于性能考虑。还有一种情况是你获得了指向完全未受管控内存的指针 - 基本上是交互操作场景 - 但这些情况比性能使用要少得多。 - Marc Gravell
嗨,马克,谢谢。马克给了我一个非常有用的例子 - https://dev59.com/NWUp5IYBdhLWcg3wY25V - Swab.Jat

4
不安全代码块很少被使用,通常只有在想要创建和利用指针时才会使用它们。此外,任何在不安全代码块中的代码都不受CLR控制。从CLR的角度来看,它被认为是不可验证的(来自MSDN)。
现在,当垃圾回收发生时,一些对象可能会被重新定位。当前缀带有fixed时,我们告诉框架不要重新定位其地址指针所指向的对象。

1
请注意,“Fixed”是一个Interop问题。当您获得指针并将其交给非托管代码(例如本机dll以在其中转储一些数据)时,您不能在调用正在进行时移动该对象的垃圾收集器。因此需要将其固定在一个位置上。 - TomTom
1
这就是MSDN所说的 - 修复内存位置,我明白了。但是为什么呢?我们为什么需要这种语言特性的任何实际用途呢?谢谢。 - Swab.Jat
1
大多数情况下,当进行Interop和Marshalling时,您需要保留指针以供更长时间使用。 - danish

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