С#: Использование типов указателей в качестве полей?

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

unsafe struct Node
{
  public Node* NextNode;
}

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

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

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

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

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

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

Ответ 1

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

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

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

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

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

Правильно.

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

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

является указателем, гарантирующим, что вы указываете на то, что вы указали на него?

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

Путь один: сообщите сборщику мусора, чтобы он не перемещал память. Это можно сделать двумя способами:

  • Исправьте переменную на месте с помощью "фиксированного" оператора.

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

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

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

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

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

Ответ 2

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

struct Node
{
    public Node NextNode;
}

или по крайней мере:

struct Node
{
    public IntPtr NextNode;
}

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

Ответ 3

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

Вы можете исправить такие объекты:

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

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

Ответ 4

Некоторые очевидные проверки целостности были исключены. Очевидная проблема заключается в том, что вам нужно выделить больше, чем вам нужно, потому что вы не можете перераспределить буфер, поскольку это означает ключевое слово 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;
}

Ответ 5

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

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

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

Ответ 6

Возможно, я слишком молод, но зачем вы хотите использовать указатель в С#?

Разве этот код не подходит для самостоятельной привязки?

public class Node
{
    //this is by-ref 
    public Node Next;
}