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移动指针。