C# 为什么返回带有方法的指针会使测试在调试模式下失败?
当我在发布模式下启动以下测试时,它们都通过了,但在调试模式下它们都失败了C# 为什么返回带有方法的指针会使测试在调试模式下失败?,c#,pointers,nunit,unsafe,C#,Pointers,Nunit,Unsafe,当我在发布模式下启动以下测试时,它们都通过了,但在调试模式下它们都失败了 [TestFixture] public unsafe class WrapperTests { [Test] public void should_correctly_set_the_size() { var wrapper = new Wrapper(); wrapper.q->size = 1; Assert.AreEqual(1, wra
[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
是一个结构,所以varRep=new Rep()
将rep
数据存储在堆栈上(当前堆栈帧是构造函数调用)
q=&rep
将获得指向rep
的指针,因此q
指向堆栈上的数据。这是这里真正的问题,因为一旦构造函数退出,它使用的堆栈空间就被认为是自由的和可重用的
在调试模式下调用rep()
时,会创建更多堆栈帧。其中一个会覆盖q
指针指向的地址处的数据
在释放模式下,对rep()
的调用由JIT内联,并创建较少的堆栈帧。但是问题仍然存在,它只是隐藏在您的示例中,因为您没有进行足够的函数调用
例如,此测试在发布模式下无法通过,原因是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移动,就像它被固定一样
不会将分配的内存归零,因此如果需要,您应该手动清除结构字段,或者如果结构较大,则使用P/Invoke调用AllocHGlobal
ZeroMemory