为什么用方法返回指针会使测试在调试模式下失败? [英] Why does returning a pointer with a method makes the test fail in debug mode?

查看:110
本文介绍了为什么用方法返回指针会使测试在调试模式下失败?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

但是,如果我删除了 rep()方法并公开了 q 指针,则测试会在调试和发布模式下通过.

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

我不知道是什么原因导致这种行为的?
为什么当我使用一种方法返回q的值时测试失败?

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 ?

推荐答案

Rep是一个结构,因此var rep = new Rep();rep数据存储在堆栈中(当前堆栈帧是构造函数调用).

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;将获得一个指向rep的指针,因此q指向堆栈上的数据.这是真正的问题,因为一旦构造函数退出,它所使用的堆栈空间就被认为是空闲且可重用的.

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.

在调试模式下调用rep()时,将创建更多的堆栈帧.其中之一将覆盖您的q指针指向的地址处的数据.

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.

在释放模式下,JIT内联到rep()的调用,并且创建的堆栈帧更少.但是问题仍然存在,因为您没有进行足够的函数调用,所以问题一直隐藏在您的示例中.

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.

例如,仅由于Split调用,此测试不会在发布模式下通过:

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.

一些注意事项:

  • 有一个终结器可以释放内存
  • 内存不会被GC移动,就像被固定一样
  • AllocHGlobal不会将分配的内存清零,因此如果需要,您应该手动清除结构字段,如果结构很大,请使用P/Invoke调用ZeroMemory.
  • 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.

这篇关于为什么用方法返回指针会使测试在调试模式下失败?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆