Dentro de la maquina virtual de .net

Hola amig@s,


En este post voy a hacer una revisión a nivel de pila de lo que sucede en nuestra máquina virtual de .net y el CLR cuando llevamos a cabo una serie de acciones bastante frecuentes.


Antes que nada, comentaros que he optado por usar ejemplos de código en C#, lo cual espero que me perdonen aquellos adeptos de VB.net, jeje.


En primer lugar, vamos a ver cuál es el estado inicial de la pila de ejecución de un programa justo antes de invocar a un método que hemos denominado M1:


 


Una vez que comienza la ejecución de M1, su prólogo reservará espacio en la pila para las variables locales, como podemos ver a continuación:


 


En la siguiente instrucción de M1, se invoca a otro método, M2, pasándole la variable local “name”, creada anteriormente, como argumento. Podemos observar que también se apila la dirección de retorno, es decir, la dirección a la que deberá volver el PC (contador de programa) cuando finalice la ejecución del método M2:



Al comenzar la ejecución del método M2, su prólogo reserva espacio para las variables locales del método en la pila, de manera análoga a lo que previamente hizo el método M1.



Al finalizar la ejecución de M2, se devuelve el control al método M1, haciendo uso de la dirección de retorno que le habíamos indicado anteriormente.



Finalmente, M1 finaliza su ejecución y devuelve el control al método desde el que había sido invocado.



Aspectos particulares del CLR:


Consideremos la siguiente declaración…



internal class Employee {



public Int32 GetYearsEmployed () {…}


public virtual String GetProgressReport () {…}


public static Employee Lookup (String name) {…}


}


internal sealed class Manager : Employee {



public override String GetProgressReport () {…}


}


Un programa en ejecución está a punto de llamar a M3, la pila y el heap ya han sido creados.



El compilador Just-In-Time (de ahora en adelante, JIT) compila M3 a código nativo. Se consultan las cabeceras de los ensamblados de los tipos Employee, Int32, Manager y String, y se crean las estructuras para representarlos (no se muestran las de Int32 y String):



Todos los objetos del heap contienen al menos dos miembros: un puntero a un objeto de tipo (type object) y un índice usado para sincronización de hilos de ejecución (sync bloc index). Los objetos de tipo Employee contienen, por tanto, ambos. También incluyen espacio para los campos estáticos de cada tipo y una tabla con los métodos definidos.


Ya está todo listo, sigamos con la ejecución del código nativo M3… [;)]



El prólogo de M3 reserva espacio en la pila para las variables locales, CLR las inicializa a NULL o a cero automáticamente.


Se crea en el heap una instancia del tipo Manager. Este objeto contiene todas las variables de instancia del tipo Manager y de todas sus clases base.



CLR inicializa un puntero al tipo del objeto y todas las variables de instancia a NULL o a cero. Después se llama al constructor del tipo y, finalmente, se guarda en la variable ‘e’ la referencia al objeto.



Llamada a un método estático


Consideremos el siguiente código:



internal class Employee {



public Int32 GetYearsEmployed () {…}


public virtual String GetProgressReport () {…}


public static Employee Lookup (String name) {…}


}


internal sealed class Manager : Employee {



public override String GetProgressReport () {…}


}


CLR busca en la tabla del tipo Employee el método Lookup. El método se compila (si es necesario) con el compilador JIT y se invoca el código resultante.



Supongamos que Lookup construye un nuevo objeto de tipo Manager en el heap y devuelve su dirección. El primer objeto Manager se convierte en un objeto obvio para el recolector de basura…


Llamada a un método de instancia no virtual


Sea el siguiente código de declaración:



internal class Employee {



public Int32 GetYearsEmployed () {…}


public virtual String GetProgressReport () {…}


public static Employee Lookup (String name) {…}


}


internal sealed class Manager : Employee {



public override String GetProgressReport () {…}


}


 


El CLR buscaría en el objeto de tipo Employee, por ser el tipo de ‘e’. Si Employee no definiera el método, iría descendiendo por la jerarquía de tipos buscándolo.


Llamada a un método virtual


Dada la siguiente declaración:



internal class Employee {


public Int32 GetYearsEmployed () {…}


public virtual String GetProgressReport () {…}


public static Employee Lookup (String name) {…}


}


internal sealed class Manager : Employee {


public override String GetProgressReport () {…}


}


El CLR seguiría la dirección de ‘e’ hasta el objeto del heap y desde éste llega al tipo real del objeto, Manager, y al objeto.



Si el método Lookup hubiera devuelto un objeto del tipo Employee, uno de los punteros habría sido distinto y la llamada a GetProgressReport habría ejecutado la implementación de Employee, en lugar de la de Manager.


Los punteros a objetos de tipo de los propios objetos de tipo apuntan a un objeto especial creado para el tipo System.Type. Los objetos de tipo Manager y Employee son “instancias” de este tipo.



El método GetType de System.Object devuelve la dirección almacenada en el puntero a objeto del tipo del objeto correspondiente. Así puede determinarse el tipo real de cada objeto.


Y por hoy nada más, que sino te ahogas [:P]


Espero que haya sido de vuestro agrado, y gracias a los que habéis aguantado hasta aquí… que no es poco!!


Un saludo [:)]

6 comentarios en “Dentro de la maquina virtual de .net”

  1. Muy bien Miguel… pero a pesar de ser muy didáctico creo que en algún caso no se acerca a la realidad. Me explico, en el primer ejemplo se reserva un espacio en la pila para una variable local de tipo String, al menos que el código que ejecutes tenga a nivel de ensamblado el atributo CompilationRelaxationAttribute con el flag NoStringInterning esto no será cierto puesto que el compilador para estos string constantes crea un diccionario para almacenarlo para mejorar el rendimiento. Como ves esto es muy pero que muy friki… pero ya que entramos dentro del CLR 🙂

    Bueno post titán…

    Saludos
    Unai

  2. Tomo nota, gracias crack!

    Espero que la observación al primer ejemplo nada más no signifique que sólo te has leído hasta ahí, jajaja

    Si es que nunca viene mal repasar conceptos a bajo nivel… siempre aprendes algo nuevo 🙂

    Nos vemos en el Code Camp? 😀

  3. Por supuesto que me lo he leído todo, y si, nos vemos en el CodeCamp… tendrás la ‘desgracia’ de volver a tragarte una de mis charlas 🙂

    Hasta pronto
    Unai

  4. Te recomiendo el libro “Common Language Runtime via C#”, realiza un análisis bastante bueno del CLR 1.1, las imágenes están ahí 🙂

    (No de versiones posteriores, de ahí que gente como Unai me haya hecho ciertas puntualizaciones de optimización que en dichas versiones no estaba).

    ¿Quién me ha copiado? ¿Quién eres?

    Comentario misterioso el tuyo, compañero de uni… jejeje

    Un saludo 🙂

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *