c# - sexuales - tipos de violaciòn
¿Por qué*(int*) 0=0 no causa una violación de acceso? (5)
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.
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 retocando los programas que causan una AccessViolationException
.
La forma más obvia (para mí) de hacer esto fue escribir en una ubicación de memoria protegida, como esta:
System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0);
Tal como esperaba, esto arrojó 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 puntero cero.
unsafe
{
*(int*)0 = 0;
}
Por razones que me eluden, esto arroja una NullReferenceException
. AccessViolationException
un AccessViolationException
con eso y descubrí que usar *(int*)1
lugar también arroja una NullReferenceException
, pero si usas un número negativo, como *(int*)-1
arrojará una AccessViolationException
.
¿Que está pasando aqui? ¿Por qué *(int*)0 = 0
causa una NullReferenceException
, y por qué no causa una AccessViolationException
?
Esta no es una respuesta per se, pero si descompila WriteInt32
, encontrará que atrapa NullReferenceException
y lanza una 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.
MSDN lo dice claramente:
En los programas que consisten en su totalidad de código administrado verificable, todas las referencias son válidas o nulas, y las violaciones de acceso son imposibles. Una excepción AccessViolationException ocurre solo cuando el código administrado verificable interactúa con un código no administrado o con un código administrado inseguro.
Ver la ayuda de AccessViolationException .
Se produce 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 null ha sido desreferenciado? ¿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 KB 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 da 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, es razonable preguntarse por qué no solo hacen 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. Imagine, por ejemplo, lo que intentó hacer:
int* p = (int*)0;
int x = p[1];
El compilador lo traduce en el equivalente moral de:
int* p = (int*)0;
int x = *( (int*)((int)p + 1 * sizeof(int)));
que es la desreferenciación 4. Pero desde la perspectiva del usuario, p[1]
seguramente parece una desreferencia de nulo! Entonces ese es el error que se informa.
NullReferenceException establece que "La excepción que se produce cuando hay un intento de desreferencia de una referencia de objeto nulo" , por lo que *(int*)0 = 0
intenta establecer la ubicación de memoria 0x000 utilizando una desreferencia de objeto arrojará una NullReferenceException
. Tenga en cuenta que esta Excepción se lanza antes de tratar de acceder incluso a la memoria.
La clase AccessViolationException por otro lado establece que, "La excepción que se produce cuando hay un intento de leer o escribir en la memoria protegida" , y dado que System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0)
no utiliza una desreferencia, en cambio, intenta establecer la memoria utilizando este método, un objeto no se desreferencia, por lo tanto, significa que no se lanzará NullReferenceException
.