Pregunta C #: ¿Usar tipos de puntero como campos?


En C #, es posible declarar una estructura (o clase) que tiene un miembro de tipo puntero, como este:

unsafe struct Node
{
  public Node* NextNode;
}

¿Alguna vez es seguro (err ... ignorar por un momento ese poco irónico unsafe bandera ...) para usar esta construcción? Quiero decir para el almacenamiento a largo plazo en el montón. Por lo que entiendo, el GC es libre de mover cosas, y mientras actualiza las referencias a algo que se ha movido, ¿actualiza también los punteros? Supongo que no, lo que haría esta construcción muy insegura, ¿verdad?

Estoy seguro de que hay alternativas superiores para hacer esto, pero llamarlo curiosidad morbosa.

EDITAR: Parece haber algo de confusión. Sé que esta no es una gran construcción, puramente Quiere saber si esta es una construcción segura, es decir: ¿está el puntero garantizado para seguir apuntando a lo que originalmente lo señaló?

El código C original se utilizó para atravesar un árbol (profundidad primero) sin recursión, donde el árbol se almacena en una matriz. A continuación, se recorre el conjunto incrementando un puntero, a menos que se cumpla una determinada condición, luego el puntero se establece en el PróximoNodo, donde continúa el recorrido. Por supuesto, lo mismo puede lograrse en C # de la siguiente manera:

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

Donde el int es el índice en la matriz del siguiente nodo. Pero por motivos de rendimiento, terminaba jugando con punteros y fixed arrays para evitar verificaciones de límites de todos modos, y el código C original parecía más natural.


11
2017-11-09 21:09


origen


Respuestas:


¿Es seguro usar esta construcción? Quiero decir para el almacenamiento a largo plazo en el montón.

Sí. Hacerlo suele ser tonto, doloroso e innecesario, pero es posible.

Por lo que entiendo, el GC es libre de mover cosas, y mientras actualiza las referencias a algo que se ha movido, ¿actualiza también los punteros?

No. Es por eso que hacemos que lo marques como inseguro.

Supongo que no, lo que haría esta construcción muy insegura, ¿verdad?

Correcto.

Estoy seguro de que hay alternativas superiores para hacer esto, pero llamarlo curiosidad morbosa.

Ciertamente hay

¿Está el puntero garantizado para seguir apuntando a lo que originalmente lo apunta?

No, a menos que se asegure de que eso suceda. Hay dos maneras de hacerlo.

Forma uno: Dígale al recolector de basura que no mueva la memoria. Hay dos maneras de hacerlo:

  • Corrige una variable en su lugar con la instrucción "fija".

  • Use los servicios de interoperabilidad para crear un identificador de CG para las estructuras que desea mantener con vida y en un solo lugar.

Hacer cualquiera de estas cosas con gran probabilidad arruinará el rendimiento del recolector de basura.

Modo dos: no tome referencias a la memoria que el recolector de basura puede mover. Hay dos maneras de hacerlo:

  • Solo tome direcciones de variables locales, parámetros de valores o bloques asignados por pila. Por supuesto, al hacerlo, se le pedirá que se asegure de que los punteros no sobrevivan más tiempo que el marco de apilamiento relevante; de ​​lo contrario, estará haciendo referencia a la basura.

  • Asigne un bloque fuera del montón no administrado y luego use punteros dentro de ese bloque. En esencia, implemente su propio administrador de memoria. Debe implementar correctamente su nuevo administrador de memoria personalizado. Ten cuidado.


12
2017-11-10 00:54



Sí, el recolector de basura puede mover los objetos y, no, no actualizará sus punteros. Debes arreglar los objetos a los que apunta. Más información se puede encontrar en este explicación de gestión de memoria.

Puedes arreglar objetos como este:

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

Las ventajas de los punteros suelen ser el rendimiento y la interacción con otros códigos no seguros. No habrá cheques fuera de límites, etc., acelerando su código. Pero igual que si estuvieras programando en, por ejemplo, C tienes que tener mucho cuidado con lo que estás haciendo.


2
2017-11-09 21:16



Algunas verificaciones de integridad obvias han sido excluidas. El problema obvio con esto es que debe asignar más de lo que necesitará porque no puede reasignar el búfer como palabra clave fijo implica.

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
2017-11-09 21:26



Una idea peligrosa, pero puede funcionar:

Cuando su conjunto de estructuras excede un cierto tamaño (85000 bytes) se asignará en el Montón de objetos grandes donde se escanean y recogen los bloques pero no se mueven ...

El artículo vinculado señala el peligro de que una versión CLR más nueva pueda mover cosas en el LOH ...


2
2017-11-09 22:46



Por qué no:

struct Node
{
    public Node NextNode;
}

o al menos:

struct Node
{
    public IntPtr NextNode;
}

Podrías usar el fijo declaración para evitar que el GC mueva los punteros.


1
2017-11-09 21:10