为啥返回带有方法的指针会使测试在调试模式下失败?
Posted
技术标签:
【中文标题】为啥返回带有方法的指针会使测试在调试模式下失败?【英文标题】:Why does returning a pointer with a method makes the test fail in debug mode?为什么返回带有方法的指针会使测试在调试模式下失败? 【发布时间】:2014-12-07 16:41:16 【问题描述】:当我在发布模式下启动以下测试时,它们都通过了,但在调试模式下它们都失败了。
[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 的值时测试会失败?
【问题讨论】:
【参考方案1】:Rep
是一个结构体,因此 var rep = 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 移动,就像它被固定一样AllocHGlobal
不会将分配的内存归零,因此如果需要,您应该手动清除结构字段,或者如果结构很大,请使用 P/Invoke 调用 ZeroMemory
。
【讨论】:
以上是关于为啥返回带有方法的指针会使测试在调试模式下失败?的主要内容,如果未能解决你的问题,请参考以下文章