2014-12-07 47 views
2

當我在發佈模式下啓動以下測試時,它們都通過,但在調試模式下它們都失敗。爲什麼使用方法返回指針會導致測試在調試模式下失敗?

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

但是如果刪除代表()方法,使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()時,會創建更多堆棧幀。其中一個覆蓋指針所指向的地址處的數據。

在釋放模式下,通過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
相關問題