¿Cómo se almacenan las variables en un compilador o intérprete de idiomas?

8

Digamos que establecemos una variable en Python.

five = 5

Auge. Lo que me pregunto es, ¿cómo se almacena esto? ¿El compilador o el intérprete lo ponen en una variable como esta?

varname = ["five"]
varval  = [5]

Si así es como se hace, ¿dónde se almacena? Parece que esto podría continuar para siempre.

baranskistad
fuente

Respuestas:

13

Interprete

Un intérprete trabajará sobre la forma en que lo adivinó. En un modelo simple, mantendrá un diccionario con los nombres de las variables como claves del diccionario y los valores de las variables como valor del diccionario. Si el lenguaje conoce el concepto de variables que son visibles solo en contextos específicos, el intérprete mantendrá múltiples diccionarios para reflejar los diferentes contextos. El intérprete en sí es típicamente un programa compilado, por lo que para su almacenamiento, consulte a continuación.

Compilador

(Esto depende mucho del idioma y del compilador y está extremadamente simplificado, por lo que solo tiene la intención de dar alguna idea).

Digamos que tenemos una variable global int five = 5. Una variable global existe solo una vez en el programa, por lo que el compilador reserva un área de memoria de 4 bytes (tamaño int) en un área de datos. Puede usar una dirección fija, digamos 1234. En el archivo ejecutable, el compilador coloca la información de que los cuatro bytes que comienzan en 1234 son necesarios como memoria de datos estáticos, deben llenarse con el número 5 al inicio del programa y opcionalmente (para soporte del depurador) la información a la que se llama el lugar 1234 fivey contiene un número entero. Dondequiera que otra línea de código se refiera a la variable nombrada five, el compilador recuerda que se coloca en 1234 e inserta una instrucción de lectura o escritura de memoria para la dirección 1234.

Si int six = 6es una variable local dentro de una función, debería existir una vez por cada llamada activa de esta función (puede haber múltiples debido a la recursión o al subprocesamiento múltiple). Por lo tanto, cada llamada de función apila suficiente espacio en la pila para contener sus variables (incluidos cuatro bytes para nuestra sixvariable. El compilador decide dónde colocar la sixvariable dentro de este marco de pila, tal vez a 8 bytes desde el inicio del marco y recuerda esa posición relativa. Entonces, las instrucciones que produce el compilador para la función son:

  • avance el puntero de la pila en suficientes bytes para todas las variables locales de la función.

  • almacene el número 6 (valor inicial de six) en la ubicación principal 8 bytes por encima del puntero de la pila.

  • donde quiera que se refiera la función six, el compilador inserta una instrucción de lectura o escritura para la ubicación de memoria 8 bytes por encima del puntero de la pila.

  • cuando termine con la función, rebobine el puntero de la pila a su valor anterior.

Una vez más, ese es solo un modelo muy simplificado, que no cubre todos los tipos de variables, pero tal vez ayude a comprender ...

Ralf Kleberhoff
fuente
Fácil de entender para alguien de mi nivel de conocimiento. ¡Muchas gracias! ¡Realmente arrojas algo de luz sobre esto!
baranskistad
Se vuelve aún más loco cuando el compilador no está compilando todo esto en un binario monolítico, sino más bien pequeñas unidades de compilación (por ejemplo, una por archivo fuente). Luego, todo lo externo (como los globales) tiene que resolver sus direcciones más tarde, mediante una herramienta separada llamada enlazador (que combinará todos estos pequeños archivos "objeto" en un binario más grande).
Kat
Además, si uno está familiarizado con las versiones anteriores de C, la naturaleza del funcionamiento de las variables locales hace obvio por qué C solía requerir que todas las variables locales se declararan en la parte superior de la función. Más tarde, decidimos que era lo suficientemente factible como para analizar primero toda la función, de modo que finalmente pudiera tener declaraciones variables donde quisiera.
Kat
Finalmente, quizás también sea relevante que no haya nada que le impida usar un diccionario para variables locales con un compilador. Sin duda, facilitaría la escritura dinámica (los tipos dinámicos no se pueden almacenar completamente en la pila, ya que no tienen un tamaño conocido en el momento de la compilación). Simplemente los diccionarios son lentos. Por cierto, la mayoría de los lenguajes compilados usan tipeo estático.
Kat
10

Depende de la implementación.

Por ejemplo, un compilador de C podría mantener una tabla de símbolos durante la compilación. Esta es una estructura de datos rica que permite empujar y hacer estallar los ámbitos, ya que cada llave de apertura de declaración compuesta {potencialmente introduce un nuevo alcance para nuevas variables locales. Además de manejar ámbitos que van y vienen, registra las variables declaradas y para cada uno incluye los nombres y sus tipos.

Esta estructura de datos de la tabla de símbolos también admite la búsqueda de información de una variable por nombre, por ejemplo, por su identificador, y el compilador hace esto cuando vincula la información de la variable declarada a los identificadores sin procesar que ve en el análisis, por lo que esto es bastante temprano durante la compilación.

En algún momento, el compilador asigna ubicaciones a las variables. Quizás las asignaciones de ubicación se registran en la misma estructura de datos de la tabla de símbolos. El compilador podría realizar la asignación de ubicación directamente durante el análisis, pero es probable que pueda hacer un mejor trabajo si espera no solo hasta después del análisis, sino también después de la optimización general.

En algún momento, para las variables locales, el compilador asigna una ubicación de pila o un registro de CPU (puede ser más complejo ya que la variable puede tener múltiples ubicaciones, como una ubicación de pila para algunas partes del código generado y una CPU registrarse para otras secciones).

Finalmente, el compilador genera código real: instrucciones de máquina que hacen referencia a los valores de las variables directamente por sus registros de CPU o ubicación de pila asignada, según sea necesario para ejecutar el código que se está compilando. Cada línea de código fuente compila su propia serie de instrucciones de código de máquina, por lo que las instrucciones generadas codifican no solo las operaciones (sumar, restar) sino también las ubicaciones de las variables a las que se hace referencia.

El código objeto final que sale del compilador ya no tiene nombres y tipos de variables; solo hay ubicaciones, ubicaciones de pila o registros de CPU. Además, no hay una tabla de ubicaciones, sino que estas instrucciones son utilizadas por cada instrucción de máquina que conoce la ubicación donde se almacena el valor de la variable. Sin buscar identificadores en el código de tiempo de ejecución, cada bit del código generado simplemente conoce la operación a realizar y las ubicaciones a utilizar.

Cuando se habilita la depuración durante la compilación, el compilador generará una forma de la tabla de símbolos para que, por ejemplo, los depuradores conozcan los nombres de las variables en las distintas ubicaciones de la pila.

Algunos otros idiomas tienen la necesidad de buscar identificadores dinámicamente en tiempo de ejecución, por lo que también pueden proporcionar algún tipo de tabla de símbolos para satisfacer tales necesidades.


Los intérpretes tienen una amplia gama de opciones. Pueden mantener una estructura de datos similar a una tabla de símbolos para usar durante la ejecución (además de usar durante el análisis), aunque en lugar de asignar / rastrear una ubicación de pila, simplemente almacene el valor de la variable, asociado con la entrada de la variable en la tabla de símbolos estructura de datos.

Quizás se almacene una tabla de símbolos en el montón en lugar de en la pila (aunque ciertamente es posible usar la pila para ámbitos y variables, y además puede imitar una pila en el montón para obtener la ventaja amigable de la caché de empacar los valores de la variable cerca de cada uno otro), por lo que un intérprete probablemente esté usando memoria de almacenamiento dinámico para almacenar los valores de la variable, mientras que un compilador usa ubicaciones de pila. En términos generales, el intérprete tampoco tiene la libertad de usar los registros de la CPU como almacenamiento para los valores de las variables, ya que los registros de la CPU están ocupados ejecutando las líneas de código del propio intérprete ...

Erik Eidt
fuente
-3

La mejor manera de comprender en qué se está compilando su código es compilar su código para ensamblarlo . El código de ensamblaje está más cerca de las instrucciones del procesador que se están ejecutando.

Samuel
fuente
1
Me parece que el OP no pregunta por el código compilado, sino por lo que sucede durante la compilación cuando el compilador analiza el código.
user1118321