Un po 'di sicurezza.
Come menzionato nei commenti alla prima soluzione, gli array annidati potrebbero essere spostati, quindi dovrebbero essere anch'essi bloccati.
unsafe
{
double[][] array = new double[3][];
array[0] = new double[] { 1.25, 2.28, 3, 4 };
array[1] = new double[] { 5, 6.24, 7.42, 8 };
array[2] = new double[] { 9, 10.15, 11, 12.14 };
GCHandle[] pinnedArray = new GCHandle[array.Length];
double*[] ptrArray = new double*[array.Length];
for (int i = 0; i < array.Length; i++)
{
pinnedArray[i] = GCHandle.Alloc(array[i], GCHandleType.Pinned);
}
for (int i = 0; i < array.Length; ++i)
{
// as you can see, this pointer will point to the first element of each array
ptrArray[i] = (double*)pinnedArray[i].AddrOfPinnedObject();
}
// here is your double**
fixed(double** doublePtr = &ptrArray[0])
{
Console.WriteLine(**doublePtr);
}
// unpin all the pinned objects,
// otherwise they will live in memory till assembly unloading
// even if they will went out of scope
for (int i = 0; i < pinnedArray.Length; ++i)
pinnedArray[i].Free();
}
una breve spiegazione del problema:
Quando allochiamo alcuni oggetti nell'heap, potrebbero essere spostate in un'altra posizione la raccolta dei rifiuti. Quindi, immagina la prossima situazione: hai assegnato qualche oggetto e i tuoi array interni, sono tutti posizionati nella generazione zero sullo heap.
Ora, qualche oggetto è passato dal campo di applicazione ed è diventato spazzatura, alcuni oggetti appena stati assegnati. Il garbage collector sposta gli oggetti vecchi fuori dall'heap e sposta altri oggetti più vicino al mendicante o persino alla generazione successiva, compattando l'heap. La volontà risultato è simile:
Quindi, il nostro obiettivo è quello di “pin” alcuni oggetti in mucchio, in modo che non si sarebbe mosso. Cosa dobbiamo raggiungere questo obiettivo? Abbiamo la dichiarazione fixed e il metodo GCHandle.Allocate.
Innanzitutto, cosa fa GCHandle.Allocate
? Crea una nuova voce nella tabella di sistema interna che ha un riferimento all'oggetto passato al metodo come parametro. Quindi, quando il garbage collector esaminerà l'heap, controllerà la tabella interna per le voci e se ne troverà una, contrassegnerà l'oggetto come vivo e non lo sposterà da heap. Quindi, vedrà come questo oggetto è bloccato e non sposterà l'oggetto nella memoria in fase di compattazione. L'istruzione fixed
fa quasi la stessa cosa, ad eccezione del fatto che l'oggetto "Unpins" viene automaticamente disattivato quando si lascia l'ambito.
Riepilogando: ogni oggetto che è stato bloccato con fixed
verrà automaticamente "sbloccato" una volta lasciato un ambito. Nel nostro caso, sarà sulla prossima iterazione del ciclo.
Come controllare che gli oggetti non vengano spostati o raccolti inutilmente: basta consumare tutto il budget dell'heap per generare zero e forzare il GC all'heap compatto. In altre parole: crea molti oggetti sullo heap. E fallo dopo aver bloccato gli oggetti o "riparato".
for(int i = 0; i < 1000000; ++i)
{
MemoryStream stream = new MemoryStream(10);
//make sure that JIT will not optimize anything, make some work
stream.Write(new Byte[]{1,2,3}, 1, 2);
}
GC.Collect();
Piccolo avviso: ci sono due tipi di heap: per oggetti grandi e per piccoli. Se il tuo oggetto è di grandi dimensioni, dovresti creare oggetti di grandi dimensioni per controllare il tuo codice, altrimenti piccoli oggetti non forzeranno GC ad avviare la garbage collection e la compattazione.
Infine, ecco alcuni esempi di codice, che dimostrano i pericoli di accesso agli array sottostanti con puntatori non appuntati/non fissati - per chiunque sia interessato.
namespace DangerousNamespace
{
// WARNING!
// This code includes possible memory access errors with unfixed/unpinned pointers!
public class DangerousClass
{
public static void Main()
{
unsafe
{
double[][] array = new double[3][];
array[0] = new double[] { 1.25, 2.28, 3, 4 };
array[1] = new double[] { 5, 6.24, 7.42, 8 };
array[2] = new double[] { 9, 10.15, 11, 12.14 };
fixed (double* junk = &array[0][0])
{
double*[] arrayofptr = new double*[array.Length];
for (int i = 0; i < array.Length; i++)
fixed (double* ptr = &array[i][0])
{
arrayofptr[i] = ptr;
}
for (int i = 0; i < 10000000; ++i)
{
Object z = new Object();
}
GC.Collect();
fixed (double** ptrptr = &arrayofptr[0])
{
for (int i = 0; i < 1000000; ++i)
{
using (MemoryStream z = new MemoryStream(200))
{
z.Write(new byte[] { 1, 2, 3 }, 1, 2);
}
}
GC.Collect();
// should print 1.25
Console.WriteLine(*(double*)(*(double**)ptrptr));
}
}
}
}
}
}
Credo che questa domanda si riassuma in: puoi convertire da un puntatore singolo (a un oggetto) a puntatore a un puntatore? – zvolkov
La soluzione con il metodo di estensione "ToPointer" è una cattiva idea, perché in questo caso si utilizzerà il puntatore all'esterno dell'area "fissa" in cui il runtime .NET potrebbe spostare l'array in un'altra posizione di memoria – Greg
Un design più intelligente Microsoft dovrebbe aver considerato: Rendi "fissa" una proprietà sottostante degli oggetti array, quindi rendi gli array convertibili in puntatori (corrispondenti a C/C++) senza bisogno di una "istruzione" fissa. In questo modo quando converti una matrice in un puntatore, come parte della conversione, l'array si "ripara" automaticamente da solo indefinitamente (poiché non c'è modo di prevedere per quanto tempo il puntatore potrebbe essere vivo). Non riesco a pensare a nessun altro modo funzionale e sicuro per implementare il supporto puntatore di array in un linguaggio OOP gestito - e personalmente ritengo che Microsoft sia stata piuttosto miope a non averlo notato. – Giffyguy