当我在发布模式下启动以下测试时,它们都通过,但是在调试模式下,它们都失败了。
[TestFixture]
public unsafe class WrapperTests
{
[Test]
public void should_correctly_set_the_size()
{
var wrapper = new Wrapper();
wrapper.q->size = 1;
Assert.AreEqual(1, wrapper.rep()->size); // Expected 1 But was: 0
}
[Test]
public void should_correctly_set_the_refcount()
{
var wrapper = new Wrapper();
Assert.AreEqual(1, wrapper.rep()->refcount); // Expected 1 But was:508011008
}
}
public unsafe class Wrapper
{
private Rep* q;
public Wrapper()
{
var rep = new Rep();
q = &rep;
q->refcount = 1;
}
public Rep* rep()
{
return q;
}
}
public unsafe struct Rep
{
public int refcount;
public int size;
public double* data;
}
但是,如果我删除rep()方法并公开q指针,则测试将同时通过调试和发布模式。
[TestFixture]
public unsafe class WrapperTests
{
[Test]
public void should_correctly_set_the_size()
{
var wrapper = new Wrapper();
wrapper.q->size = 1;
Assert.AreEqual(1, wrapper.q->size);
}
[Test]
public void should_correctly_set_the_refcount()
{
var wrapper = new Wrapper();
Assert.AreEqual(1, wrapper.q->refcount);
}
}
public unsafe class Wrapper
{
public Rep* q;
public Wrapper()
{
var rep = new Rep();
q = &rep;
q->refcount = 1;
}
}
public unsafe struct Rep
{
public int refcount;
public int size;
public double* data;
}
我不明白什么会导致这种行为?
为什么使用方法返回q的值时测试失败?
最佳答案
Rep
是一个结构,因此var rep = new Rep();
将rep
数据存储在堆栈上(当前堆栈帧是构造函数调用)。q = &rep;
将获得指向rep
的指针,因此q
指向堆栈上的数据。这是真正的问题,因为一旦构造函数退出,它所使用的堆栈空间就被认为是空闲且可重用的。
在调试模式下调用rep()
时,将创建更多的堆栈帧。其中之一将覆盖您的q
指针指向的地址处的数据。
在释放模式下,JIT内联到rep()
的调用,并且创建的堆栈帧更少。但是问题仍然存在,因为您未进行足够的函数调用,因此问题一直隐藏在示例中。
例如,仅由于Split
调用,此测试不会在发布模式下通过:
[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
不会将分配的内存清零,因此如果需要,您应该手动清除结构字段,如果结构很大,请使用P / Invoke调用ZeroMemory
。关于c# - 为什么用方法返回指针会使测试在 Debug模式下失败?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27344978/