2011-12-30 18 views
25

Per scopi didattici, sto scrivendo una serie di metodi che causano eccezioni di runtime in C# per capire quali sono tutte le eccezioni e cosa le causa. In questo momento, sto armeggiando con programmi che causano uno AccessViolationException.Perché * (int *) 0 = 0 non causa una violazione di accesso?

Il modo più ovvio (per me) per fare questo è stato quello di scrivere in una posizione di memoria protetta, in questo modo:

System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0); 

Proprio come avevo sperato, questo ha gettato un AccessViolationException. Volevo farlo in modo più conciso, così ho deciso di scrivere un programma con codice non sicuro, e fare (quello che pensavo fosse) esattamente la stessa cosa assegnando 0 al puntatore zero.

unsafe 
{ 
    *(int*)0 = 0; 
} 

Per ragioni che mi sfuggono, questo getta un NullReferenceException. Ne ho giocato un po 'e ho scoperto che l'utilizzo di *(int*)1 lancia anche un NullReferenceException, ma se si utilizza un numero negativo, come *(int*)-1, verrà generato un valore AccessViolationException.

Cosa sta succedendo qui? Perché lo *(int*)0 = 0 causa uno NullReferenceException e perché non causa uno AccessViolationException?

+1

'(int *) 0' è un puntatore nullo. Mi aspetterei pienamente un 'NullReferenceException'. Se vuoi una 'AccessViolationException', prova qualcosa come' (int *) 0x10' (o possibilmente '0xf0000000'). – cHao

+7

possibile duplicato di [Perché l'accesso alla memoria è nello spazio degli indirizzi più basso (sebbene non nulla) segnalato come NullReferenceException da .NET?] (Http://stackoverflow.com/questions/7940492/why-is-memory-access-in -the-lowest-address-space-non-null-though-reported-as-n) –

risposta

28

Un'eccezione di riferimento null si verifica quando si disattiva un puntatore nullo; il CLR non si cura se il puntatore nullo è un puntatore non sicuro con lo zero intero bloccato in esso o un puntatore gestito (ovvero un riferimento a un oggetto di tipo di riferimento) con zero bloccato in esso.

Come fa il CLR a sapere che il riferimento è stato cancellato? E come fa il CLR a sapere quando qualche altro puntatore non valido è stato dereferenziato? Ogni puntatore punta da qualche parte in una pagina di memoria virtuale nello spazio di indirizzamento della memoria virtuale del processo. Il sistema operativo tiene traccia di quali pagine sono valide e quali non sono valide; quando si tocca una pagina non valida si solleva un'eccezione che viene rilevata dal CLR. Il CLR quindi emerge come eccezione di accesso non valido o eccezione di riferimento null.

Se l'accesso non valido è in fondo a 64 KB di memoria, si tratta di un'eccezione di riferimento null. Altrimenti è un'eccezione di accesso non valida.

Questo spiega perché il dereferenziamento zero e uno danno un'eccezione di riferimento null e perché il dereferenziamento -1 fornisce un'eccezione di accesso non valida; -1 è puntatore 0xFFFFFFFF su macchine a 32 bit e quella pagina particolare (su macchine x86) è sempre riservata al sistema operativo da utilizzare per i propri scopi. Il codice utente non può accedervi.

Ora, si potrebbe ragionevolmente chiedere perché non eseguire solo l'eccezione di riferimento null per il puntatore zero e l'eccezione di accesso non valido per tutto il resto? Poiché la maggior parte delle volte in cui un piccolo numero è dereferenziato, è perché ci si è arrivati ​​tramite un riferimento null. Immaginate per esempio che si è tentato di fare:

int* p = (int*)0; 
int x = p[1]; 

Il compilatore traduce che in l'equivalente morale di:

int* p = (int*)0; 
int x = *((int*)((int)p + 1 * sizeof(int))); 

che è dereferenziazione 4. Ma dal punto di vista dell'utente, p[1] appare sicuramente come un dereference di null! Quindi questo è l'errore segnalato.

+0

Risposta fantastica (e molto interessante)! +1 –

2

Le NullReferenceException stati che "L'eccezione che viene generata quando si verifica un tentativo di dereference un riferimento oggetto null", così dato *(int*)0 = 0 tenta di impostare locazione di memoria 0x000 utilizzando un oggetto dereferenziarlo lancerà una NullReferenceException. Si noti che questa eccezione viene lanciata prima di tentare di accedere anche alla memoria.

La classe AccessViolationException sugli altri stati della mano che, "L'eccezione che viene generata quando si verifica un tentativo di leggere o scrivere memoria protetta", e dal momento che System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0) non usa un dereference, invece cerca di impostare la memoria utilizzando questo metodo, un oggetto non viene dereferenziato, pertanto non viene generato alcuno NullReferenceException.

+1

Questo non è completamente vero. 'WriteInt32' _does_ dereferenzia il puntatore, ma rileva l'NRE e al suo posto genera una violazione di accesso. – Daniel

+0

Questa risposta è sbagliata. La risposta corretta è qui: http://stackoverflow.com/a/7940659/162396 – Daniel

4

Questa non è una risposta di per sé, ma se si decompila WriteInt32 si trova che cattura NullReferenceException e genera un AccessViolationException. Quindi il comportamento è probabilmente lo stesso, ma è mascherato dalla vera eccezione che viene catturata e viene sollevata un'eccezione diversa.

1

MSDN dice che chiaramente:

Nei programmi composti interamente di codice verificabile gestito, tutti riferimenti sono o valido o nullo, e le violazioni di accesso sono impossibile. Una AccessViolationException si verifica solo quando il codice gestito verificabile interagisce con codice non gestito o con codice gestito non sicuro.

Vedere la guida di AccessViolationException.

+0

Questo non spiega perché i due frammenti generano eccezioni diverse. – Daniel

+0

Daniel, in effetti lo sta spiegando. Non l'ho elaborato in quanto vi è già una spiegazione dettagliata di JSPerfUnkn0wn. – Yakeen

+0

Daniel, la spiegazione di Hans su cui ti riferisci sembra ragionevole. – Yakeen

0

Ecco come funziona CLR. Invece di controllare se l'indirizzo dell'oggetto == null per ogni accesso al campo, basta accedervi. Se era nullo, CLR recupera GPF e lo ripropone come NullReferenceException. Non importa che tipo di riferimento fosse.

Problemi correlati