2011-12-30 8 views
25

Para propósitos educativos, estoy escribiendo un conjunto de métodos que causan excepciones de tiempo de ejecución en C# para comprender qué son todas las excepciones y qué las causa. En este momento, estoy jugando con los programas que causan un AccessViolationException.¿Por qué * (int *) 0 = 0 no causa una infracción de acceso?

La manera más obvia (para mí) para hacer esto era escribir en una posición de memoria protegida, así:

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

Tal como lo había esperado, esta arrojó una AccessViolationException. Quería hacerlo de manera más concisa, así que decidí escribir un programa con código inseguro, y hacer (lo que pensé que era) exactamente lo mismo asignando 0 al cero.

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

Por razones que escapan a mí, esto arroja una NullReferenceException. Jugué un poco con eso y descubrí que usar *(int*)1 en su lugar también arroja un NullReferenceException, pero si usa un número negativo, como *(int*)-1 arrojará un AccessViolationException.

¿Qué está pasando aquí? ¿Por qué *(int*)0 = 0 causa un NullReferenceException, y por qué no causa un AccessViolationException?

+1

'(int *) 0' es un puntero nulo. Esperaría una 'NullReferenceException'. Si desea una 'AccessViolationException', intente algo como' (int *) 0x10' (o posiblemente '0xf0000000'). – cHao

+7

posible duplicado de [¿Por qué el acceso a la memoria en el espacio de direcciones más bajo (no nulo, sin embargo) informó como NullReferenceException por .NET?] (Http://stackoverflow.com/questions/7940492/why-is-memory-access-in -the-lowest-address-space-non-null-though-reported-as-n) –

Respuesta

28

Ocurre una excepción de referencia nula cuando se desreferencia un puntero nulo; al CLR no le importa si el puntero nulo es un puntero inseguro con el cero entero pegado en él o un puntero administrado (es decir, una referencia a un objeto de tipo de referencia) con cero pegado en él.

¿Cómo sabe el CLR que se ha desreferenciado el nulo? ¿Y cómo sabe el CLR cuando se ha desreferenciado algún otro puntero no válido? Cada apuntador apunta a algún lugar en una página de memoria virtual en el espacio de dirección de la memoria virtual del proceso. El sistema operativo realiza un seguimiento de qué páginas son válidas y cuáles no válidas. cuando tocas una página no válida, se genera una excepción que el CLR detecta. El CLR luego lo muestra como una excepción de acceso no válido o como una excepción de referencia nula.

Si el acceso no válido está en la parte inferior de 64 K de memoria, es una excepción de ref nula. De lo contrario, es una excepción de acceso no válido.

Esto explica por qué la desreferenciación de cero y uno dan una excepción de ref nula, y por qué dereferenciación -1 da una excepción de acceso no válida; -1 es el puntero 0xFFFFFFFF en máquinas de 32 bits, y esa página en particular (en máquinas x86) siempre está reservada para que el sistema operativo la use para sus propios fines. El código de usuario no puede acceder a él.

Ahora, podría preguntarse razonablemente por qué no solo hacer la excepción de referencia nula para el puntero cero y la excepción de acceso no válido para todo lo demás. Porque la mayoría de las veces cuando se desreferencia un número pequeño, es porque se llegó a él a través de una referencia nula. Imagínese, por ejemplo, que ha intentado hacer:

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

El compilador traduce esto en el equivalente moral de:

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

que se dereferencing 4. Sin embargo, desde la perspectiva del usuario, p[1] sin duda se parece a un desreferenciar de nulo! Entonces ese es el error que se informa.

+0

¡Fantástica (y muy interesante) respuesta! +1 –

2

Los estados que NullReferenceException"La excepción que se produce cuando hay un intento de eliminar la referencia de una referencia de objeto nulo", por lo que desde *(int*)0 = 0 trata de establecer la posición de memoria 0x000 utilizando un objeto eliminar la referencia que arrojará un NullReferenceException. Tenga en cuenta que esta Excepción se lanza antes de tratar de acceder incluso a la memoria.

La clase AccessViolationException en los otros estados de mano que, "La excepción que se produce cuando hay un intento de leer o escribir en la memoria protegida", y desde System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0) no utiliza una falta de referencia, en lugar trata de fijar la memoria utilizando este método, un objeto no se desreferencia, por lo tanto, no se lanzará el significado NullReferenceException.

+1

Eso no es del todo cierto. 'WriteInt32' _does_ desreferencia el puntero, pero captura el NRE y arroja una infracción de acceso en su lugar. – Daniel

+0

Esta respuesta es incorrecta. La respuesta correcta está aquí: http://stackoverflow.com/a/7940659/162396 – Daniel

4

Esto no es una respuesta per se, pero si descompila WriteInt32 le resulta que atrapa NullReferenceException y arroja un AccessViolationException. Por lo tanto, es probable que el comportamiento sea el mismo, pero está enmascarado por la excepción real que se detecta y se plantea una excepción diferente.

1

El MSDN dice que claramente:

En los programas compuestos en su totalidad de código verificable administrado, todos referencias son ya sea válida o nula, y violaciónes de acceso son imposible. Una excepción AccessViolationException ocurre solo cuando el código administrado verificable interactúa con el código no administrado o con el código administrado inseguro .

Ver AccessViolationException ayuda.

+0

Eso no explica por qué los dos fragmentos arrojan diferentes excepciones. – Daniel

+0

Daniel, de hecho lo está explicando. No lo he elaborado porque ya hay una explicación detallada de JSPerfUnkn0wn. – Yakeen

+0

Daniel, la explicación de Hans sobre tu referencia parece razonable. – Yakeen

0

Así es como funciona CLR. En lugar de verificar si la dirección del objeto == null para cada acceso de campo, solo tiene que acceder a ella. Si fue nulo, CLR captura GPF y lo vuelve a lanzar como NullReferenceException. No importa qué tipo de referencia era.

Cuestiones relacionadas