C #: использование типов указателей в качестве полей? - PullRequest
12 голосов
/ 10 ноября 2009

В C # можно объявить структуру (или класс), которая имеет член типа указателя, например:

unsafe struct Node
{
  public Node* NextNode;
}

Всегда ли безопасно (ошибаться ... на мгновение игнорировать этот ироничный маленький unsafe флаг ...) использовать эту конструкцию? Я имею в виду долгосрочное хранение в куче. Из того, что я понимаю, GC может свободно перемещать вещи, и, хотя он обновляет ссылки на что-то, что было перемещено, он также обновляет указатели? Я предполагаю нет, что сделало бы эту конструкцию очень небезопасной, верно?

Я уверен, что есть альтернативы для этого, но назовите это болезненным любопытством.

РЕДАКТИРОВАТЬ: Кажется, есть некоторая путаница. Я знаю, что это не очень хорошая конструкция, я чисто хочу знать, является ли она когда-либо безопасной конструкцией, то есть: гарантированно ли указатель будет продолжать указывать на то, на что вы изначально указывали?

Исходный C-код использовался для обхода дерева (сначала глубиной) без рекурсии, где дерево хранится в массиве. Затем массив перемещается путем увеличения указателя, если только не выполнено определенное условие, тогда указатель устанавливается на NextNode, где обход продолжается. Конечно, то же самое можно сделать в C #:

struct Node
{
  public int NextNode;
  ... // other fields
}

Где int - это индекс в массиве следующего узла. Но по соображениям производительности я бы в конечном итоге возился с указателями и массивами fixed, чтобы избежать проверки границ в любом случае, и оригинальный C-код казался более естественным.

Ответы [ 5 ]

12 голосов
/ 10 ноября 2009

Всегда ли безопасно использовать эту конструкцию? Я имею в виду для длительного хранения в куче.

Да. Делать это обычно глупо, больно и не нужно, но это возможно.

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

Нет. Вот почему мы заставляем вас пометить его как небезопасный.

Полагаю, нет, что сделало бы эту конструкцию очень небезопасной, верно?

Правильно.

Я уверен, что есть альтернативы этому, но назовите это болезненным любопытством.

Конечно, есть.

гарантированно ли указатель будет продолжать указывать на то, на что вы изначально указывали?

Нет, если вы не уверены, что это произойдет. Есть два способа сделать это.

Путь первый: Скажите сборщику мусора не перемещать память. Есть два способа сделать это:

  • Исправить переменную на месте с помощью оператора «fixed».

  • Используйте сервисы взаимодействия для создания дескриптора gc для структур, которые вы хотите сохранить и в одном месте.

Выполнение любого из этих действий с высокой вероятностью разрушит производительность сборщика мусора.

Способ второй: не берите ссылки на память, которую может переместить сборщик мусора. Есть два способа сделать это:

  • Берут только адреса локальных переменных, параметров значений или блоков, выделенных в стеке. Конечно, при этом вам необходимо убедиться, что указатели не доживут дольше, чем соответствующий кадр стека, в противном случае вы ссылаетесь на мусор.

  • Выделите блок из неуправляемой кучи, а затем используйте указатели внутри этого блока. В сущности, реализовать свой собственный менеджер памяти. Вы должны правильно реализовать свой новый менеджер памяти. Будьте осторожны.

2 голосов
/ 10 ноября 2009

Опасная идея, но она может сработать:

Когда ваш массив структур превышает определенный размер (85000 байт), он будет размещен в куче больших объектов , где блоки сканируются и собираются, но не перемещаются ...

В связанной статье указывается на опасность того, что более новая версия CLR может переместить материал на LOH ...

2 голосов
/ 10 ноября 2009

Да, сборщик мусора может перемещать объекты и, нет, он не будет обновлять ваши указатели. Вы должны исправить объекты, на которые вы указываете. Более подробную информацию можно найти в этом объяснении управления памятью .

Вы можете исправить объекты следующим образом:

  unsafe {
     fixed (byte* pPtr = object) {
         // This will fix object in the memory
        }
     }
  }

Преимуществами указателей обычно являются производительность и взаимодействие с другим небезопасным кодом. Там не будет никаких проверок и т. Д., Ускоряющих ваш код. Но так же, как если бы вы программировали, например, C вы должны быть очень осторожны в том, что вы делаете.

2 голосов
/ 10 ноября 2009

Некоторые очевидные проверки целостности были исключены. Очевидная проблема с этим заключается в том, что вы должны выделить больше, чем вам нужно, потому что вы не можете перераспределить буфер, как предполагает ключевое слово fixed .

public unsafe class NodeList
{
    fixed Node _Nodes[1024];
    Node* _Current;

    public NodeList(params String[] data)
    {
        for (int i = 0; i < data.Length; i++)
        {
            _Nodes[i].Data = data[i];
            _Nodes[i].Next = (i < data.Length ? &_Nodes[i + 1] : null);     
        }

        _Current = &_Nodes[0];
    }

    public Node* Current()
    {
        return _Current++;  
    }
}

public unsafe struct Node
{
    public String Data;
    public Node* Next;
}
2 голосов
/ 10 ноября 2009

Почему бы и нет:

struct Node
{
    public Node NextNode;
}

или хотя бы:

struct Node
{
    public IntPtr NextNode;
}

Вы можете использовать оператор fixed , чтобы запретить GC перемещать указатели.

...