在C#中,可以声明一个具有指针类型成员的结构(或类),如下所示:
unsafe struct Node
{
public Node* NextNode;
}
使用这种结构是否安全(错误......暂时忽略具有讽刺性的小unsafe
旗...)?我的意思是在堆上长期存储。根据我的理解,GC可以自由移动,当它更新对已被移动的内容的引用时,它是否也更新了指针?我猜不是,这会使这种结构非常不安全,对吗?
我确信这样做有更好的选择,但称之为病态的好奇心。
编辑:似乎有些混乱。我知道这不是一个很好的结构,我纯粹想知道这是否是一个安全的结构,即:指针是否保证指向你最初指向它的东西?原始C代码用于遍历树(深度优先)而不进行递归,其中树存储在数组中。然后通过递增指针遍历数组,除非满足某个条件,然后将指针设置为NextNode,其中遍历继续。当然,在C#中也可以通过以下方式完成:
struct Node
{
public int NextNode;
... // other fields
}
int
是下一个节点数组中的索引。但是出于性能原因,我最终会使用指针和fixed
数组进行调整以避免边界检查,并且原始的C代码看起来更自然。
答案 0 :(得分:12)
使用这种结构是否安全?我的意思是在堆上长期存储。
是。这样做通常是愚蠢,痛苦和不必要的,但这是可能的。
根据我的理解,GC可以随意移动,当它更新对已被移动的内容的引用时,它是否也会更新指针?
没有。 这就是我们让您将其标记为不安全的原因。
我猜不,这会使这种结构非常不安全,对吗?
正确。
肯定有。我确信这样做有更好的选择,但称之为病态的好奇心。
是保证指向你最初指向它的指针的指针吗?
除非您确保发生这种情况。有两种方法可以做到这一点。
方法一:告诉垃圾收集器不要移动内存。有两种方法可以做到这一点:
使用“fixed”语句修复变量。
使用互操作服务在一个地方为您希望保持活动的结构创建一个gc句柄。
执行上述任何一项操作都很可能会破坏垃圾收集器的性能。
方式二:不要引用垃圾收集器可能移动的内存。有两种方法可以做到这一点:
仅获取局部变量,值参数或堆栈分配块的地址。当然,在这样做时,您需要确保指针的存活时间不会超过相关的堆栈帧,否则,您将引用垃圾。
从非托管堆中分配一个块,然后在该块内使用指针。在本质上,实现自己的内存管理器。您需要正确实现新的自定义内存管理器。小心。
答案 1 :(得分:2)
为什么不:
struct Node
{
public Node NextNode;
}
或至少:
struct Node
{
public IntPtr NextNode;
}
您可以使用fixed语句来阻止GC移动指针。
答案 2 :(得分:2)
是的,垃圾收集器可以移动对象,不,它不会更新你的指针。您需要修复指向的对象。有关详细信息,请参阅此memory management explanation。
你可以修复这样的对象:
unsafe {
fixed (byte* pPtr = object) {
// This will fix object in the memory
}
}
}
指针的优点通常是性能和与其他不安全代码的交互。没有越界检查等,加速你的代码。但就像你在编程中一样C你必须非常小心你正在做的事情。
答案 3 :(得分:2)
已排除一些明显的完整性检查。显而易见的问题是,您必须分配超出您需要的数量,因为您无法重新分配缓冲区,因为关键字已修复意味着。
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;
}
答案 4 :(得分:2)
一个危险的想法,但它可能有效:
当你的结构数组超过一定的大小(85000字节)时,它将被分配到Large Object Heap,在那里扫描和收集块但不移动...
链接文章指出了较新的CLR版本可能会在LOH上移动内容的危险......