C#:将指针类型用作字段?

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

unsafe struct Node { public Node* NextNode; } 

它是否安全(错误……暂时忽略了具有讽刺性的小unsafe旗帜……)使用这种结构? 我的意思是在堆上长期存储。 根据我的理解,GC可以随意移动,当它更新对已被移动的内容的引用时,它是否也会更新指针? 我猜不是,这会使这种结构非常不安全,对吧?

我确信这样做有更好的选择,但称之为病态的好奇心。

编辑:似乎有些混乱。 我知道这不是一个伟大的结构,我纯粹想知道这是否是一个安全的结构,即:指针保证指向你最初指向的任何东西?

原始的C代码用于遍历树(深度优先)而不进行递归,其中树存储在数组中。 然后通过递增指针遍历数组,除非满足某个条件,然后将指针设置为NextNode,其中遍历继续。 当然,在C#中也可以通过以下方式实现:

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

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

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

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

根据我的理解,GC可以随意移动,当它更新对已被移动的内容的引用时,它是否也会更新指针?

不, 这就是我们让你把它标记为不安全的原因。

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

正确。

我确信这样做有更好的选择,但称之为病态的好奇心。

肯定有。

是指针保证指向你最初指向它的东西?

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

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

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

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

执行这些操作中的任何一个都很可能会破坏垃圾收集器的性能。

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

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

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

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

你可以修复这样的对象:

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

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

一些明显的完整性检查已被排除。 显而易见的问题是你必须分配超过你需要的数量,因为你不能像固定的关键字那样重新分配缓冲区。

 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; } 

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

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

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

为什么不:

 struct Node { public Node NextNode; } 

或至少:

 struct Node { public IntPtr NextNode; } 

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