Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
Este será casi con seguridad mi último post del 2006, pero eso no lo puedo asegurar ya que cuando me pica la curiosidad, siento esa irresistible necesidad de saciarla. Y eso es lo que me ha ocurrido esta mañana. Como había comentado antes no había usado punteros en c# y este hecho me ha picado, de modo que he tratado ver como son manejados por el CLR.
Bien, todo era normal hasta que he visto este código IL en donde no entendía el misterioso conv.u, el código que estaba usando es el siguiente:
public static unsafe float TestPointer2()
{
float f = -128.625f;
int* i = (int*) &f;
float* fp = (float*)i;
return *fp;
}
Mi sesión de depuración:
!clrstack -a PDB symbol for mscorwks.dll not loaded OS Thread Id: 0xb7c (2940) ESP EIP 0012f3f4 00e600ed TestCastPointer.Test.TestPointer2() LOCALS: 0x0012f400 = 0x00000000 <CLR reg> = 0x00000000 <CLR reg> = 0x00000000 0x0012f3f4 = 0x00000000 0012f444 00e6009a TestCastPointer.Program.Main(System.String[]) PARAMETERS: args = 0x01321b5c 0012f69c 79e88f63 [GCFrame: 0012f69c] !ip2md 00e600ed MethodDesc: 00973078 Method Name: TestCastPointer.Test.TestPointer2() Class: 009713d0 MethodTable: 00973090 mdToken: 06000003 Module: 00972c14 IsJitted: yes m_CodeOrIL: 00e600c0 !dumpil 00973078 ilAddr = 0040208c IL_0000: nop IL_0001: ldc.r4 -128.625000 //Carga (push) el valor float (4bytes) en el stack IL_0006: stloc.0 //Saca del stack y carga en la variable 0 IL_0007: ldloca.s VAR OR ARG 0 //Carga la dirección de la variable local existente //en la dirección (0) en el stack de evaluación. IL_0009: conv.u //Aquí este misterioso conv.u que se supone convierte //el valor del stack en un unsigned int IL_000a: stloc.1 //Saca el valor (pop) del stack y lo carga en var 1
Estado del stack 0x0012f3f4 = 0xc300a000 (-128,625f) CLR reg = 0x0012f3f4 (var 1)
IL_000b: ldloc.1 //Carga la var 1 en el stack de evaluación IL_000c: stloc.2 //Saca el valor (pop) del stack y lo carga en var 2 IL_000d: ldloc.2 //Carga la var 2 en el stack de evaluación IL_000e: ldind.r4 //Carga en la pila un float indirectamente IL_000f: stloc.3 IL_0010: br.s IL_0012 IL_0012: ldloc.3 IL_0013: ret
Obviamente, hay muchas cosas que todavía no entiendo del IL y menos el ensablador que es generado por el JIT, pero de eso hablaré el año que viene. LA cuestión es la siguiente, es ese conv.u. He puesto como se queda el estado de la pila del CLR tras ejecutar el IL_000a. La reflexión que me he hecho es la siguiente; si lo que hemos colocado en el stack es una dirección de memoria, en este caso la de la variable f, para que quiero convertirla a un int, ¿no es la dirección de memoria un int32? Tras darle algunas vueltas he visto el código que genera el JIT, ¿donde esta el conv.u?
25: float f = -128.625f; 0000002e mov dword ptr [ebp-3Ch],0C300A000h //carga el valor en [ebp-3Ch] (f) 26: int* i = (int*) &f; 00000035 lea eax,[ebp-3Ch] //carga en el acumulador la dirección de f 00000038 mov edi,eax //mueve el acumulador al índice de destino (edi) 27: 28: float* fp = (float*)i; 0000003a mov esi,edi //mueve el índice de destino al índice fuente (esi) 29: return *fp; 0000003c fld dword ptr [esi] //carga el contenido del índice fuente como float 0000003e fstp dword ptr [ebp-48h] //almacena el contenido del puntero base - 48h 00000041 nop 00000042 jmp 00000044 30: } 00000044 fld dword ptr [ebp-48h] //prepara el stack para la devolución cargando el float
Entonces ha sido cuando leyendo el Ecma-335 (1.1.4.2 Managed Pointers) me he encontrado con esto:Managed pointers that do not point to managed memory can be converted (using conv.u or conv.ovf.u) into unmanaged pointers, but this is not verifiable. ¿tendrá que ver con esto?, yo creo que me quedo aquí.