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
是一个结构,所以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