12

在 C# 中,可以声明具有指针类型成员的结构(或类),如下所示:

unsafe struct Node
{
  public Node* NextNode;
}

使用这种结构是否安全(错误..暂时忽略那个具有讽刺意味的小unsafe标志..)?我的意思是长期存储在堆上。据我了解,GC 可以自由移动事物,并且在更新对已移动事物的引用时,它是否也更新指针?我猜不,这会使这种结构非常不安全,对吧?

我敢肯定有更好的替代方法来做到这一点,但称之为病态的好奇心。

编辑:似乎有些混乱。我知道这不是一个很好的结构,我纯粹想知道这是否是一个安全的结构,即:是否保证指针始终指向您最初指向的任何内容?

原始的 C 代码用于在没有递归的情况下遍历树(深度优先),其中树存储在数组中。然后通过增加一个指针来遍历该数组,除非满足某个条件,否则将指针设置为 NextNode,继续遍历。当然,在 C# 中也可以通过以下方式完成:

struct Node
{
  public int NextNode;
  ... // other fields
}

其中int是下一个节点的数组中的索引。但是出于性能原因,我最终还是会摆弄指针和fixed数组以避免边界检查,而原始的 C 代码似乎更自然。

4

5 回答 5

12

使用这种结构是否安全?我的意思是长期存储在堆上。

是的。这样做通常是愚蠢、痛苦和不必要的,但这是可能的。

据我了解,GC 可以自由移动事物,并且在更新对已移动事物的引用时,它是否也更新指针?

,这就是我们让您将其标记为不安全的原因。

我猜不,这会使这种结构非常不安全,对吧?

正确的。

我敢肯定有更好的替代方法来做到这一点,但称之为病态的好奇心。

当然有。

指针是否保证始终指向您最初指向的任何内容?

除非你确保发生这种情况。有两种方法可以做到这一点。

方法一:告诉垃圾收集器不要移动内存。有两种方法可以做到这一点:

  • 使用“fixed”语句修复变量。

  • 使用互操作服务为您希望在一个地方保持活动的结构创建一个 gc 句柄。

做这些事情很可能会破坏垃圾收集器的性能。

方式二:不要引用垃圾收集器可能移动的内存。有两种方法可以做到这一点:

  • 只取局部变量、值参数或堆栈分配块的地址。当然,在这样做时,您需要确保指针的生存时间不会超过相关的堆栈帧,否则,您将引用垃圾。

  • 从非托管堆中分配一个块,然后在该块内使用指针。本质上,实现您自己的内存管理器。您需要正确实现新的自定义内存管理器。当心。

于 2009-11-10T00:54:42.507 回答
3

一些明显的完整性检查已被排除。一个明显的问题是你必须分配比你需要的更多的东西,因为你不能像关键字fix所暗示的那样重新分配缓冲区。

public unsafe class NodeList
{
    fixed Node _Nodes[1024];
    Node* _Current;

    public NodeList(params String[] data)
    {
        for (int i = 0; i < data.Length; i++)
        {
            _Nodes[i].Data = data[i];
            _Nodes[i].Next = (i < data.Length ? &_Nodes[i + 1] : null);     
        }

        _Current = &_Nodes[0];
    }

    public Node* Current()
    {
        return _Current++;  
    }
}

public unsafe struct Node
{
    public String Data;
    public Node* Next;
}
于 2009-11-09T21:26:01.617 回答
2

为什么不:

struct Node
{
    public Node NextNode;
}

或者至少:

struct Node
{
    public IntPtr NextNode;
}

您可以使用fixed语句来防止 GC 移动指针。

于 2009-11-09T21:10:55.423 回答
2

是的,垃圾收集器可以移动对象,不,它不会更新您的指针。您需要修复您指向的对象。更多信息可以在这个内存管理解释中找到。

您可以像这样修复对象:

  unsafe {
     fixed (byte* pPtr = object) {
         // This will fix object in the memory
        }
     }
  }

指针的优点通常是性能和与其他不安全代码的交互。不会有越界检查等,从而加快您的代码速度。但是就像你在用 C 语言编程一样,你必须非常小心你在做什么。

于 2009-11-09T21:16:46.760 回答
2

一个危险的想法,但它可能有效:

当您的结构数组超过一定大小(85000 字节)时,它将被分配在大对象堆上,在该堆中扫描和收集块但不移动......

链接的文章指出了较新的 CLR 版本可能会在 LOH 上移动内容的危险......

于 2009-11-09T22:46:34.823 回答