Why does returning a pointer with a method makes the test fail in debug mode?

二次信任 提交于 2020-01-05 02:25:30

问题


When I launch the following tests in Release mode, they both pass, but in Debug mode they both fail.

[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;
}

However if I remove the rep() method and make the q pointer public, tests pass both in debug and release mode.

[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;
}

I don't understand what can cause this behavior?
Why does the test fail when I use a method to return the value of q ?


回答1:


Rep is a struct, so var rep = new Rep(); will store the rep data on the stack (the current stack frame being the constructor call).

q = &rep; will get a pointer to rep, therefore q points to data on the stack. This is the real issue here, because as soon as the constructor exits, the stack space it used is considered free and reusable.

When you call rep() in debug mode, more stack frames are created. One of them overwrites the data at the address your q pointer points to.

In release mode the call to rep() is inlined by the JIT and less stack frames are created. But the problem persists, it's just hidden in your example because you didn't make enough function calls.

For instance, this test won't pass in release mode, just because of theSplit call:

[Test]
public void should_correctly_set_the_refcount()
{
    var wrapper = new Wrapper();
    "abc,def".Split(',');
    Assert.AreEqual(1, wrapper.rep()->refcount);
}

As a general rule, you shouldn't ever let pointers outlive the data they point to.

To solve your issue, you can allocate some unmanaged memory, like that:

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;
    }
}

This passes all your tests.

Some points to note:

  • There's a finalizer that frees the memory
  • The memory won't be moved by the GC, just like if it were pinned
  • AllocHGlobal doesn't zero out the allocated memory, so you should clear the structure fields manually if needed, or call ZeroMemory with P/Invoke if the structure is large.


来源:https://stackoverflow.com/questions/27344978/why-does-returning-a-pointer-with-a-method-makes-the-test-fail-in-debug-mode

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!