uso parametros metodos metodo funciones firma ejemplos declaracion consola con clases c# x86 jit

parametros - metodos y funciones en c#



¿Por qué los métodos de instancia de estructura C#que invocan métodos de instancia en un campo de estructura primero comprueban ecx? (1)

Respuesta corta: el JITter no puede probar que la estructura no está referenciada por un puntero, y debe al menos desreferenciarse al menos una vez en cada llamada a NoOp () para el comportamiento correcto.

Respuesta larga: las estructuras son extrañas.

El JITter es conservador. Siempre que sea posible, solo puede optimizar el código de forma que sea absolutamente seguro para producir el comportamiento correcto. "Mayormente correcto" no es lo suficientemente bueno.

Así que ahora hay un ejemplo de escenario que se rompería si el JITter optimiza la desreferencia. Considere los siguientes hechos:

Primero: recuerde que las estructuras pueden (y existen) fuera de C #: por ejemplo, un puntero a un StructDispatch podría provenir de un código no administrado. Como Lucas señaló, puedes usar punteros para hacer trampa; pero el JITter no puede estar seguro de que no está utilizando los punteros para StructDispatch en otro lugar del código.

Segundo: recuerde que en el código no administrado, que es la razón principal por la que existen las estructuras, todas las apuestas están desactivadas. Solo porque acaba de leer un valor de la memoria no significa que será el mismo valor o incluso que sea un valor la próxima vez que lea la misma dirección exacta. El subprocesamiento y el multiprocesamiento pueden hacer que, literalmente, algo cambie ese valor en el siguiente reloj, por no hablar de actores que no tienen CPU, como DMA. Un hilo paralelo podría VirtualFree () la página que contiene esa estructura, y el JITter debe protegerse contra eso. Usted pidió lecturas de la memoria, por lo que obtiene lecturas de la memoria. Mi conjetura es que si usted pateara el optimizador, eliminaría una de esas instrucciones de cmp, pero dudo mucho que eliminaría ambas.

Tercero: las excepciones también son códigos reales. NullReferenceException no necesariamente detiene el programa; Puede ser atrapado y manejado. Eso significa que, desde la perspectiva del JITter, NRE es más como una declaración if que un goto: es un tipo de rama de condición que debe manejarse y considerarse en cada desreferencia de memoria.

Así que ahora pon esas piezas juntas.

El JITter no sabe, y no puede saber, que no está utilizando C # inseguro o una fuente externa en otro lugar para interactuar con la memoria de StructDispatch. No produce implementaciones separadas de CallViaStruct (), una para "código C # probablemente seguro" y una para "código externo posiblemente riesgoso"; Produce la versión conservadora para escenarios posiblemente arriesgados, siempre. Esto significa que no puede simplemente cortar las llamadas a NoOp () en su totalidad, porque no hay garantía de que StructDispatch no esté, por ejemplo, asignado a una dirección que ni siquiera se encuentra en la memoria.

Sabe que NoOp () está vacío y puede ser eluido (la llamada puede desaparecer), pero al menos tiene que simular el ldfla pinchando la dirección de memoria de la estructura, porque podría haber un código dependiendo de la NRE que se genera. Las desreferencias de memoria son como sentencias if: pueden causar una rama, y ​​al no hacerlo se puede romper el programa. Microsoft no puede hacer suposiciones y simplemente dice: "Su código no debe confiar en eso". Imagine la llamada telefónica enojada a Microsoft si no se escribió una NRE en el registro de errores de una empresa simplemente porque el JITter decidió que no era una NRE "lo suficientemente importante" como para activarse en primer lugar. El JITter no tiene más remedio que desreferenciar esa dirección al menos una vez para asegurar la semántica correcta.

Las clases no tienen ninguna de estas preocupaciones; no hay rareza de memoria forzada con una clase. Pero las estructuras, sin embargo, son más extravagantes.

¿Por qué el X86 para el siguiente método C # CallViaStruct incluye la instrucción cmp ?

struct Struct { public void NoOp() { } } struct StructDisptach { Struct m_struct; [MethodImpl(MethodImplOptions.NoInlining)] public void CallViaStruct() { m_struct.NoOp(); //push ebp //mov ebp,esp //cmp byte ptr [ecx],al //pop ebp //ret } }

Aquí hay un programa más completo que se puede compilar con varias descompilaciones (de lanzamiento) como comentarios. Esperaba que el X86 para CallViaStruct en los tipos ClassDispatch y StructDispatch sea ​​el mismo, sin embargo, la versión en StructDispatch (extraída arriba) incluye una instrucción cmp mientras que la otra no.

Parece que la instrucción cmp es una expresión idiomática que se utiliza para garantizar que una variable no sea nula; la desreferenciación de un registro con valor 0 activa una av que se convierte en una NullReferenceException . Sin embargo, en StructDisptach.CallViaStruct no puedo concebir una manera de que ecx sea ​​nulo dado que apunta a una estructura.

ACTUALIZACIÓN: La respuesta que busco aceptar incluirá un código que hace que el StructDisptach.CallViaStruct una NRE al tener la StructDisptach.CallViaStruct de la instrucción cmp un registro ecx cero. Tenga en cuenta que esto es fácil de hacer con cualquiera de los métodos de m_class = null estableciendo m_class = null e imposible de hacer con ClassDisptach.CallViaStruct ya que no hay una instrucción cmp .

using System.Runtime.CompilerServices; namespace NativeImageTest { struct Struct { public void NoOp() { } } class Class { public void NoOp() { } } class ClassDisptach { Class m_class; Struct m_struct; internal ClassDisptach(Class cls) { m_class = cls; m_struct = new Struct(); } [MethodImpl(MethodImplOptions.NoInlining)] public void CallViaClass() { m_class.NoOp(); //push ebp //mov ebp,esp //mov eax,dword ptr [ecx+4] //cmp byte ptr [eax],al //pop ebp //ret } [MethodImpl(MethodImplOptions.NoInlining)] public void CallViaStruct() { m_struct.NoOp(); //push ebp //mov ebp,esp //pop ebp //ret } } struct StructDisptach { Class m_class; Struct m_struct; internal StructDisptach(Class cls) { m_class = cls; m_struct = new Struct(); } [MethodImpl(MethodImplOptions.NoInlining)] public void CallViaClass() { m_class.NoOp(); //push ebp //mov ebp,esp //mov eax,dword ptr [ecx] //cmp byte ptr [eax],al //pop ebp //ret } [MethodImpl(MethodImplOptions.NoInlining)] public void CallViaStruct() { m_struct.NoOp(); //push ebp //mov ebp,esp //cmp byte ptr [ecx],al //pop ebp //ret } } class Program { static void Main(string[] args) { var classDispatch = new ClassDisptach(new Class()); classDispatch.CallViaClass(); classDispatch.CallViaStruct(); var structDispatch = new StructDisptach(new Class()); structDispatch.CallViaClass(); structDispatch.CallViaStruct(); } } }

ACTUALIZACIÓN: Resulta que es posible usar callvirt en una función no virtual que tiene un efecto secundario de nulo al comprobar el puntero de este. Si bien este es el caso del sitio de CallViaClass CallViaClass (por lo que vemos el cheque nulo allí) StructDispatch.CallViaStruct usa una instrucción de call .

.method public hidebysig instance void CallViaClass() cil managed noinlining { // Code size 12 (0xc) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld class NativeImageTest.Class NativeImageTest.StructDisptach::m_class IL_0006: callvirt instance void NativeImageTest.Class::NoOp() IL_000b: ret } // end of method StructDisptach::CallViaClass .method public hidebysig instance void CallViaStruct() cil managed noinlining { // Code size 12 (0xc) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldflda valuetype NativeImageTest.Struct NativeImageTest.StructDisptach::m_struct IL_0006: call instance void NativeImageTest.Struct::NoOp() IL_000b: ret } // end of method StructDisptach::CallViaStruct

ACTUALIZACIÓN: Hubo una sugerencia de que el cmp podría estar atrapando para el caso en el que no se haya atrapado este puntero null en el sitio de la llamada. Si ese fuera el caso, entonces esperaría que el cmp produjera una vez en la parte superior del método. Sin embargo, aparece una vez por cada llamada a NoOp :

struct StructDisptach { Struct m_struct; [MethodImpl(MethodImplOptions.NoInlining)] public void CallViaStruct() { m_struct.NoOp(); m_struct.NoOp(); //push ebp //mov ebp,esp //cmp byte ptr [ecx],al //cmp byte ptr [ecx],al //pop ebp //ret } }