Rep
是一个结构体,所以 varrep = new Rep();
将存储rep
堆栈上的数据(当前堆栈帧是构造函数调用)。
q = &rep;
将得到一个指向rep
, 所以q
指向堆栈上的数据。这是这里真正的问题,因为一旦构造函数退出,它使用的堆栈空间就被认为是免费且可重用的。
你打电话时rep()
在调试模式下,会创建更多堆栈帧。其中之一会覆盖您的地址处的数据q
指针指向。
在释放模式下调用rep()
由 JIT 内联,并且创建的堆栈帧更少。但问题仍然存在,它只是隐藏在您的示例中,因为您没有进行足够的函数调用。
例如,这个测试在发布模式下不会通过,只是因为Split
call:
[Test]
public void should_correctly_set_the_refcount()
{
var wrapper = new Wrapper();
"abc,def".Split(',');
Assert.AreEqual(1, wrapper.rep()->refcount);
}
作为一般规则,您不应该让指针比它们指向的数据更长寿。
要解决您的问题,您可以分配一些非托管内存,如下所示:
public unsafe class Wrapper
{
public Rep* q;
public Wrapper()
{
q = (Rep*)Marshal.AllocHGlobal(sizeof(Rep));
q->refcount = 1;
q->size = 0;
q->data = null;
}
~Wrapper()
{
Marshal.FreeHGlobal((IntPtr)q);
}
public Rep* rep()
{
return q;
}
}
这通过了你所有的测试。
需要注意的几点:
- 有一个释放内存的终结器
- 内存不会被 GC 移动,就像被固定一样
-
AllocHGlobal
不会将分配的内存清零,因此您应该根据需要手动清除结构字段,或者调用ZeroMemory
如果结构很大,则使用 P/Invoke。