当我在发布模式下启动以下测试时,它们都通过,但是在调试模式下,它们都失败了。

[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/

10-13 06:54