2014-12-07 2 views
2

Когда я запускаю следующие тесты в режиме Release, они оба проходят, но в режиме отладки оба они терпят неудачу.Почему возврат указателя с помощью метода приводит к отказу теста в режиме отладки?

[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() метод и сделать д указатель общественности, тесты проходят как в отладки и выпуска режиме.

[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; получит указатель на 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 не равна нулю внесите выделенную память, поэтому вам нужно очистить поля структуры вручную, если необходимо, или вызвать ZeroMemory с P/Invoke, если структура большая.
Смежные вопросы