Cuando una computadora almacena una variable, cuando un programa necesita obtener el valor de la variable, ¿cómo sabe la computadora dónde buscar en la memoria el valor de esa variable?
compilers
memory-access
variable-binding
MCMastery
fuente
fuente
Respuestas:
¡Te sugiero que mires al maravilloso mundo de Compiler Construction! La respuesta es que es un proceso un poco complicado.
Para intentar darle una intuición, recuerde que los nombres de las variables están ahí únicamente por el bien del programador. La computadora finalmente convertirá todo en direcciones al final.
Las variables locales se almacenan (generalmente) en la pila: es decir, son parte de la estructura de datos que representa una llamada a la función. Podemos determinar la lista completa de variables que una función usará (tal vez) al observar esa función, para que el compilador pueda ver cuántas variables necesita para esta función y cuánto espacio ocupa cada variable.
Hay un poco de magia llamada puntero de la pila, que es un registro que siempre almacena la dirección de donde comienza la pila actual.
Cada variable recibe un "desplazamiento de pila", que es donde se almacena en la pila. Luego, cuando el programa necesita acceder a una variable
x
, el compilador reemplazax
conSTACK_POINTER + x_offset
, para obtener el lugar físico real en el que está almacenado en la memoria.Tenga en cuenta que, es por eso que obtiene un puntero cuando usa
malloc
onew
en C o C ++. No puede determinar exactamente dónde está en la memoria un valor asignado de almacenamiento dinámico, por lo que debe mantener un puntero a él. Ese puntero estará en la pila, pero apuntará al montón.Los detalles de la actualización de las pilas para llamadas a funciones y devoluciones son complicados, por lo que recomendaría The Dragon Book o The Tiger Book si está interesado.
fuente
El programa lo cuenta. Las computadoras no tienen un concepto nativo de "variables", ¡eso es completamente un lenguaje de alto nivel!
Aquí hay un programa en C:
y aquí está el código de ensamblaje con el que compila: (comentarios que comienzan con
;
)Para "int a = 1;" la CPU ve la instrucción "almacenar el valor 1 en la dirección (valor del registro rbp, menos 4)". Sabe dónde almacenar el valor 1 porque el programa lo dice.
Del mismo modo, la siguiente instrucción dice "cargar el valor en la dirección (valor del registro rbp, menos 4) en el registro eax". La computadora no necesita saber cosas como variables.
fuente
%rsp
es el puntero de la pila de la CPU.%rbp
es un registro que se refiere al bit de la pila utilizado por la función actual. El uso de dos registros simplifica la depuración.Cuando el compilador o el intérprete encuentra la declaración de una variable, decide qué dirección usará para almacenar esa variable, y luego registra la dirección en una tabla de símbolos. Cuando se encuentran referencias posteriores a esa variable, se sustituye la dirección de la tabla de símbolos.
La dirección registrada en la tabla de símbolos puede ser un desplazamiento de un registro (como el puntero de la pila) pero eso es un detalle de implementación.
fuente
Los métodos exactos dependen de qué está hablando específicamente y qué tan profundo quiere llegar. Por ejemplo, almacenar archivos en un disco duro es diferente de almacenar algo en la memoria o almacenar algo en una base de datos. Aunque los conceptos son similares. Y cómo lo hace a nivel de programación es una explicación diferente de cómo lo hace una computadora a nivel de E / S.
La mayoría de los sistemas utilizan algún tipo de mecanismo de directorio / índice / registro para permitir que la computadora encuentre y acceda a los datos. Este índice / directorio contendrá una o más claves, y la dirección en la que se encuentran los datos (ya sea disco duro, RAM, base de datos, etc.).
Ejemplo de programa de computadora
Un programa de computadora puede acceder a la memoria de varias maneras. Típicamente, el sistema operativo le da al programa un espacio de direcciones, y el programa puede hacer lo que quiera con ese espacio de direcciones. Puede escribir directamente a cualquier dirección dentro de su espacio de memoria, y puede hacer un seguimiento de eso como lo desee. Esto a veces variará según el lenguaje de programación y el sistema operativo, o incluso según las técnicas preferidas de un programador.
Como se menciona en algunas de las otras respuestas, la codificación o programación exacta utilizada difiere, pero generalmente detrás de escena usa algo como una pila. Tiene un registro que almacena la ubicación de la memoria donde comienza la pila actual, y luego un método para saber en qué parte de esa pila se encuentra una función o variable.
En muchos lenguajes de programación de nivel superior, se encarga de todo eso por usted. Todo lo que tiene que hacer es declarar una variable y almacenar algo en esa variable, y crea las pilas y matrices necesarias detrás de escena para usted.
Pero teniendo en cuenta cuán versátil es la programación, en realidad no hay una respuesta, ya que un programador puede elegir escribir directamente a cualquier dirección dentro de su espacio asignado en cualquier momento (suponiendo que esté usando un lenguaje de programación que lo permita). Luego podría almacenar su ubicación en una matriz, o incluso simplemente codificarla en el programa (es decir, la variable "alfa" siempre se almacena al comienzo de la pila o siempre se almacena en los primeros 32 bits de memoria asignada).
Resumen
Básicamente, tiene que haber algún mecanismo detrás de escena que le indique a la computadora dónde se almacenan los datos. Una de las formas más populares es algún tipo de índice / directorio que contiene clave (s) y la dirección de memoria. Esto se implementa en todo tipo de formas y generalmente se encapsula desde el usuario (y a veces incluso se encapsula desde el programador).
Referencia: ¿Cómo recuerdan las computadoras dónde almacenan cosas?
fuente
Lo sabe por plantillas y formatos.
El programa / función / computadora en realidad no sabe dónde está nada. Solo espera que algo esté en un lugar determinado. Usemos un ejemplo.
Nuestra nueva clase 'simpleClass' contiene 3 variables importantes: dos enteros que pueden contener algunos datos cuando los necesitamos y un puntero a otro 'objeto simpleClass'. Supongamos que estamos en una máquina de 32 bits por simplicidad. 'gcc' u otro compilador 'C' crearía una plantilla con la que trabajar para asignar algunos datos.
Tipos simples
En primer lugar, cuando se usa una palabra clave para un tipo simple como 'int', el compilador toma una nota en la sección '.data' o '.bss' del archivo ejecutable para que cuando el sistema operativo lo ejecute, los datos sean disponible para el programa. La palabra clave 'int' asignaría 4 bytes (32 bits), mientras que un 'int largo' asignaría 8 bytes (64 bits).
A veces, de manera celda por celda, una variable puede aparecer justo después de la instrucción que se supone que debe cargarla en la memoria, por lo que se vería así en pseudoensamblaje:
Esto terminaría con el valor '5' en EAX y EBX.
Mientras el programa se ejecuta, todas las instrucciones se ejecutan, excepto el '5', ya que la carga inmediata hace referencia a él y hace que la CPU lo omita.
La desventaja de este método es que solo es realmente práctico para las constantes, ya que no sería práctico mantener matrices / buffers / cadenas en el medio de su código. Por lo tanto, en general, la mayoría de las variables se mantienen en los encabezados del programa.
Si fuera necesario acceder a una de estas variables dinámicas, se podría tratar el valor inmediato como si fuera un puntero:
Esto terminaría con el valor '0x0AF2CE66' en el registro EAX y el valor de '5' en el registro EBX. También se pueden agregar valores en registros juntos, por lo que podríamos encontrar elementos de una matriz o cadena usando este método.
Otro punto importante es que uno puede almacenar valores cuando usa direcciones de manera similar, para que luego pueda hacer referencia a los valores en esas celdas.
Tipos complejos
Si hacemos dos objetos de esta clase:
entonces podemos asignar un puntero al segundo objeto al campo disponible para él en el primer objeto:
Ahora el programa puede esperar encontrar la dirección del segundo objeto dentro del campo de puntero del primer objeto. En la memoria, esto se vería así:
Un hecho muy importante a tener en cuenta aquí es que 'newObjA' y 'newObjB' no tienen nombres cuando se compilan. Son solo lugares donde esperamos que estén algunos datos. Entonces, si agregamos 2 celdas a & newObjA, entonces encontramos la celda que actúa como 'nextObject'. Por lo tanto, si conocemos la dirección de 'newObjA' y donde la celda 'nextObject' es relativa a ella, entonces podemos conocer la dirección de 'newObjB':
Esto terminaría con '2+ & newObjA' en 'EAX' y '& newObjB' en 'EBX'.
Plantillas / Formatos
Cuando el compilador compila la definición de clase, realmente está compilando una forma de hacer un formato, una forma de escribir en un formato y una forma de leer desde un formato.
El ejemplo dado anteriormente es una plantilla para una lista enlazada individualmente con dos variables 'int'. Este tipo de construcciones son muy importantes para la asignación dinámica de memoria, junto con los árboles binarios y n-arios. Las aplicaciones prácticas de los árboles n-ary serían sistemas de archivos compuestos por directorios que apuntan a archivos, directorios u otras instancias reconocidas por los controladores / el sistema operativo.
Para acceder a todos los elementos, piense en un gusano de pulgada que sube y baja por la estructura. De esta manera, el programa / función / computadora no sabe nada, solo ejecuta instrucciones para mover datos.
fuente