2016-02-24 7 views

risposta

7

Il "problema" è più grande: in C# non è possibile avere un puntatore a un tipo gestito. Se si prova a scrivere (in C#):

string *pstr; 

si otterrà:

Cannot take the address of, get the size of, or declare a pointer to a managed type ('string')

Ora, stackalloc T[num] restituisce un T* (vedi ad esempio here), così chiaramente stackalloc non può essere utilizzato con tipi di riferimento.

Il motivo per cui non è possibile avere un puntatore a un tipo di riferimento è probabilmente connesso al fatto che il GC può spostare liberamente i tipi di riferimento attorno alla memoria (per compattare la memoria), quindi la validità di un puntatore potrebbe essere breve .

Si noti che in C++/CLI è possibile bloccare un tipo di riferimento e prendere il suo indirizzo (vedi pin_ptr)

+0

È possibile avere un puntatore a un tipo gestito in C#, non è incorporato nella lingua come con C++/CLI. https://msdn.microsoft.com/en-us/library/1246yz8f(v=vs.110).aspx - Usa GCHandleType.Pinned come secondo argomento, quindi chiama AddrOfPinnedObject() sul risultato. – Nuzzolilo

+0

@Nuzzolilo Hai provato? Se ricordo bene riceverai un'eccezione se provi a 'GCHandleType.Pinned' un oggetto gestito. – xanatos

+0

Ricordo di averlo fatto anni fa, ma è passato così tanto tempo che la mia memoria poteva essere sbagliata: P. Ci proverò di nuovo e vedrò cosa succede. – Nuzzolilo

0

causa C# funziona su garbage collection per safetiness di memoria, a differenza di C++, sono stati si sono tenuti a conoscere le capacità di gestione della memoria.

per esempio, dare un'occhiata al codice successivo:

public static void doAsync(){ 
    var arr = stackalloc string[100]; 
    arr[0] = "hi"; 
    System.Threading.ThreadPool.QueueUserWorkItem(()=>{ 
      Thread.Sleep(10000); 
      Console.Write(arr[0]); 
    }); 
} 

Il programma andrà in crash facilmente. poiché arr è allocato allo stack, l'oggetto + la sua memoria scomparirà non appena doAsync è finito. la funzione lamda punta ancora a questo indirizzo di memoria non più valido, e questo è uno stato non valido.

se si passano i primitivi locali per riferimento, si verificherà lo stesso problema.

Lo schema è:
oggetti statici -> vite in tutto il tempo Applocation
locale oggetto -> vive fino a quando il Ambito che li ha creati è valida
oggetti heap-assegnati (creato con new) - > esiste finché qualcuno ha un riferimento a loro.

Un altro problema è che la raccolta Garbage funziona in periodi. quando un oggetto è locale, dovrebbe essere finalizzato non appena la funzione è finita, perché dopo quel tempo - la memoria sarà sostituita da altre variabili. Il GC non può essere forzato a finalizzare l'oggetto, o non dovrebbe, comunque.

La cosa buona, però, è che il C# JIT a volte (non sempre) può determinare che un oggetto può essere assegnato in modo sicuro nello stack e ricorrere all'assegnazione dello stack, se possibile (di nuovo, a volte).

In C++ d'altra parte, è possibile dichiarare tutto enywhere, ma questo viene fornito con meno safetyness allora C# o Java, ma si può mettere a punto voi applicazione e raggiungere alte performance - a basso risorse applicazione

+0

* "quindi nessuna eccezione di memoria può accadere con le primitive." * Questo non è vero. Se invece dovessi utilizzare ints, stai ancora accedendo alla memoria liberata se tenti di accedervi dopo che l'array è stato liberato dallo stack. –

+0

Le matrici in C# sono oggetti fledge completi, quindi nessuna contraddizione in questo caso -> matrice di primitive -> oggetto con detiene primitive -> stesso problema. quando dico "primitive" intendo variabili come 'int',' bool' ecc. non compund types come array, che sono oobjects –

-1

penso Xanatos ha pubblicato la risposta corretta.

In ogni caso, questa non è una risposta, ma un controesempio ad un'altra risposta.

Si consideri il seguente codice:

using System; 
using System.Threading; 

namespace Demo 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      doAsync(); 
      Thread.Sleep(2000); 
      Console.WriteLine("Did we finish?"); // Likely this is never displayed. 
     } 

     public static unsafe void doAsync() 
     { 
      int n = 10000; 
      int* arr = stackalloc int[n]; 
       ThreadPool.QueueUserWorkItem(x => { 
       Thread.Sleep(1000); 

       for (int i = 0; i < n; ++i) 
        arr[i] = 0; 
      }); 
     } 
    } 
} 

Se si esegue il codice, andrà in crash perché la matrice dello stack vengono scritti dopo che la memoria di stack perché è stato liberato.

Questo dimostra che la ragione per cui lo stackalloc non può essere utilizzato con i tipi di riferimento non è semplicemente per prevenire questo tipo di errore.

+0

lol. ma 'int []' è un oggetto (tipo di riferimento), quindi non hai provato nulla. –

+0

@DavidHaim non c'è l'oggetto 'int []' in quel codice. –

+0

@DavidHaim Non c'è 'int []' in questo codice. 'arr' è un' int * '. –

2

Il compilatore Just-In-Time in .NET svolge due importanti funzioni quando converte MSIL come generato dal compilatore C# in codice macchina eseguibile. L'ovvio e visibile sta generando il codice macchina. Il lavoro non ovvio e completamente invisibile sta generando una tabella che dice al garbage collector dove cercare i riferimenti agli oggetti quando un GC si verifica mentre il metodo è in esecuzione.

Questo è necessario perché le radici degli oggetti non possono essere semplicemente memorizzate nell'heap GC, come un campo di una classe, ma anche memorizzate in variabili locali o registri CPU. Per eseguire correttamente questo lavoro, il jitter deve conoscere la struttura esatta dello stack frame e i tipi di variabili memorizzate, in modo che possa creare correttamente quella tabella. In questo modo, in seguito, il garbage collector può capire come leggere il corretto offset del frame dello stack o il registro della CPU per ottenere il valore root dell'oggetto. Un puntatore nell'heap GC.

Questo è un problema quando si utilizza stackalloc. Questa sintassi sfrutta una funzionalità CLR che consente a un programma di dichiarare un tipo di valore personalizzato. Una back-door attorno alle normali dichiarazioni di tipo gestito, con la restrizione che questo tipo di valore non possa contenere alcun campo. Solo un blob di memoria, spetta al programma generare gli offset corretti in quel blob. Il compilatore C# ti aiuta a generare quegli offset, in base alla dichiarazione del tipo e all'espressione dell'indice.

Anche molto comune in un programma C++/CLI, la stessa funzionalità di tipo di valore personalizzato può fornire l'archiviazione per un oggetto C++ nativo. È richiesto solo uno spazio per la memorizzazione di tale oggetto, come inizializzarlo correttamente e accedere ai membri di quell'oggetto C++ è un lavoro che il compilatore C++ calcola. Niente di ciò che il GC deve sapere.

Quindi la restrizione principale è che non esiste un modo per fornire informazioni sul tipo per questo blob di memoria. Per quanto riguarda il CLR, questi sono solo semplici byte senza struttura, la tabella utilizzata dal GC non ha alcuna opzione per descrivere la sua struttura interna.

Inevitabilmente, l'unico tipo di tipo che è possibile utilizzare è il tipo che non richiede un riferimento a un oggetto che il GC deve conoscere. Tipi di valore o puntatori non selezionabili. Quindi System.String è un no-go, è un tipo di riferimento. Il più vicino si potrebbe ottenere che è "filante" è:

char** mem = stackalloc char*[100]; 

Con l'ulteriore restrizione che è interamente a voi per assicurare che il char * elementi puntare a una stringa appuntato o non gestito. E che non indichi "array" fuori limite. Questo non è molto pratico.

Problemi correlati