2009-05-20 9 views
12

Domanda semplice: esiste un modo per convertire da una matrice frastagliata a un doppio puntatore?Conversione da una matrice frastagliata a doppio puntatore in C#

ad es. Converti uno double[][] in double**

Questo non può essere fatto semplicemente colando sfortunatamente (come invece nella semplice vecchia C), sfortunatamente. Anche l'utilizzo di un'istruzione fixed non sembra risolvere il problema. Esiste un modo (preferibilmente il più efficiente possibile) per realizzare questo in C#? Sospetto che la soluzione potrebbe non essere del tutto ovvia, anche se spero comunque che sia semplice.

+0

Credo che questa domanda si riassuma in: puoi convertire da un puntatore singolo (a un oggetto) a puntatore a un puntatore? – zvolkov

+4

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

+0

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

risposta

5

Un doppio [] [] è un array di double [], non di doppia *, in modo da ottenere una doppia **, in primo luogo abbiamo bisogno di una doppia * []

double[][] array = //whatever 
//initialize as necessary 

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; 
     } 

    fixed (double** ptrptr = &arrayofptr[0]) 
    { 
     //whatever 
    } 
} 

non posso aiutaci, ma ti chiedi a cosa serve e se c'è una soluzione migliore che richiedere un doppio puntatore.

+0

Purtroppo non posso evitare l'utente di un doppio puntatore, dal momento che sto chiamando una funzione C esterna, e C# non può eseguire il marshalling automatico di matrici frastagliate. – Noldorin

+0

Darò questo andare al più presto possibile a proposito. Grazie. – Noldorin

+0

Ho dovuto modificare questo come 6 volte per aggirare SO volendo analizzare * s come corsivo. La finestra di anteprima e il post effettivo erano incoerenti nella loro interpretazione ... – bsneeze

-6

Sono andato con la soluzione zachrrs per il momento (che era quello che sospettavo potrebbe essere necessario in primo luogo). Qui è un metodo di estensione:

public static double** ToPointer(this double[][] array) 
{ 
    fixed (double* arrayPtr = array[0]) 
    { 
     double*[] ptrArray = new double*[array.Length]; 
     for (int i = 0; i < array.Length; i++) 
     { 
      fixed (double* ptr = array[i]) 
       ptrArray[i] = ptr; 
     } 

     fixed (double** ptr = ptrArray) 
      return ptr; 
    } 
} 
+6

Non è possibile utilizzare un puntatore al di fuori del blocco 'fixed' in cui l'hai dichiarato, poiché l'oggetto potrebbe essersi spostato nel frattempo. – svick

+0

@svick: certo che puoi. Potrebbe non funzionare tutto il tempo. Come succede, in questo caso lo fa ... Forse uno dei metodi statici di 'Marshal' potrebbe però renderlo più robusto. – Noldorin

+5

Beh, forse sembra funzionare proprio ora. Ma tu cambi una linea in un non collegato o hai solo sfortuna un giorno e non funzionerà. Il tuo codice è ** sbagliato **. Il fatto che funzioni attualmente è principalmente un incidente. – svick

4

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.

enter image description here

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:

enter image description here

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)); 
        } 
       } 
      } 
     } 
    } 
} 
+0

Potresti modificare questa risposta per mostrare la creazione di un doppio **? Come accennato in un commento precedente, gli array non sono contigui, quindi è necessario creare un array separato dei puntatori aggiunti e quindi restituire un puntatore a tale array separato.Forse con un altro metodo per annullare il tutto in un secondo momento, quando il doppio ** non è più necessario. In realtà, questo sta iniziando a sembrare un buon candidato per i metodi di estensione per System.Array. :) – Giffyguy

+0

@Giffyguy beh, sì, posso, ma penso che sarà come rispondere a una domanda diversa. La necessità di creare array continui è soggettiva per me e potrebbe essere implementata in diversi modi, e questi modi dipenderanno dallo scopo iniziale e dal lato ricevente. Ma comunque, aggiungerò qualche "semplice" realizzazione di tale estensione alla mia risposta. – cassandrad

+0

@Giffyguy ora mi sono reso conto che non ho capito il tuo punto. Bene, ho modificato la risposta per mostrare dove è il doppio **. Spero che sia quello che mi hai chiesto di fare. Per la prima volta pensavo che mi aveste chiesto di scrivere una classe che potesse ottenere un array frastagliato e convertirlo in un array continuo con funzione GetAddr per esporre questo array all'esterno, con sicurezza dei thread, gestione delle risorse, IDispossable e altre interfacce SafeHandleZeroOrMinusOneIsInvalid. Passato diverse ore a implementarlo, haha. – cassandrad