2009-09-03 18 views
32

Cuando escribo un programa y le digo int c=5, pone el valor 5 en un poco de su memoria, pero ¿cómo recuerda cuál? La única forma en que podría pensar sería en tener otro poco de memoria para contarlo, pero luego tendría que recordar dónde se guardaba eso también, entonces, ¿cómo recuerda dónde está todo?¿Cómo sabe dónde está mi valor en la memoria?

+21

Creo que garabatea en un papel. Por si acaso. –

+5

+1 por una maldita buena primera pregunta ... bienvenido al redil :) – skaffman

+4

@skaffman Bien dicho. Es difícil ponerse en el lugar del principiante absoluto y creo que esta es una pregunta que realmente muestra curiosidad y pensamiento ... ¡aunque me reí en voz alta del comentario de un hacker! –

Respuesta

9

Aquí hay muchas buenas respuestas, pero todas parecen pasar por alto un punto importante que creo que fue el principal impulso de la pregunta del OP, así que aquí va. Estoy hablando de lenguajes compilados como C++, los interpretados son mucho más complejos.

Al compilar su programa, el compilador examina su código para encontrar todas las variables. Algunas variables van a ser globales (o estáticas), y algunas van a ser locales. Para las variables estáticas, les asigna direcciones de memoria fija. Es probable que estas direcciones sean secuenciales y comiencen con algún valor específico. Debido a la segmentación de la memoria en la mayoría de las arquitecturas (y los mecanismos de memoria virtual), cada aplicación puede (potencialmente) usar las mismas direcciones de memoria. Por lo tanto, si suponemos que los programas de espacio de memoria pueden usar comienza en 0 para nuestro ejemplo, cada programa que compile colocará la primera variable global en la ubicación 0. Si esa variable fuera de 4 bytes, la siguiente estaría en la ubicación 4, etc. Esto no entrará en conflicto con otros programas que se ejecutan en su sistema porque en realidad están siendo mapeados a una sección secuencial arbitraria de la memoria en tiempo de ejecución. Es por eso que puede asignar una dirección fija en tiempo de compilación sin preocuparse por golpear a otros programas.

Para las variables locales, en lugar de tener asignada una dirección fija, se les asigna una dirección fija relativa al puntero de la pila (que generalmente es un registro). Cuando se llama a una función que asigna variables en la pila, el puntero de la pila simplemente se mueve por el número requerido de bytes, creando un espacio en los bytes usados ​​en la pila. Todas las variables locales tienen desviaciones fijas asignadas al puntero de la pila que las coloca en esa brecha. Cada vez que se usa una variable local, la dirección de la memoria real se calcula sumando el puntero de pila y el desplazamiento (descuidando los valores de almacenamiento en caché en los registros). Cuando la función retorna, el puntero de la pila se restablece a la forma en que estaba antes de que se llamara a la función, por lo tanto, toda la estructura de la pila, incluidas las variables locales, puede sobrescribirse con la siguiente llamada de función.

+0

Buen trabajo al detectar la pregunta en la pregunta. – dmckee

+0

Consideré mencionar esto pero no quería complicar el problema. Bueno, ahí está. – Artelius

13

Su código se compila antes de la ejecución, en ese paso variable se sustituye por la referencia real del espacio donde se almacenará el valor.

Este es al menos el principio general. En realidad será mucho más completo, pero sigue siendo la misma idea básica.

+2

Es como la compilación vuelve a etiquetar todas las variables que tiene un número que representa una dirección en la memoria. Entonces, 'c' se convierte en '0xdeadbeef' o lo que sea. Puede volver a etiquetar por la misma razón por la que puede revisar todo su código fuente y cambiar las referencias 'c' a 'd' y el programa sigue haciendo lo mismo (suponiendo que no haya una variable 'd'). ¡definido!) –

6

leer Variable (programación) - Asignación de memoria:
http://en.wikipedia.org/wiki/Variable_(programming)#Memory_allocation

He aquí el texto del enlace (si usted no quiere ir realmente allí, pero se echa en falta todos los enlaces dentro del texto) :

los detalles de la asignación variable de y la representación de sus valores varían ampliamente, tanto entre la programación idiomas y entre las implementaciones de un idioma determinado. Muchos de lenguaje implementaciones asignar espacio para variables locales, cuya extensión dura para una sola llamada de función en la llamada pila, y cuya memoria se reclamados automáticamente cuando se devuelve la función. (Más generalmente, en la unión nombre, el nombre de una variable está obligado a la dirección de algunos bloque particular (secuencia contigua) de bytes en la memoria, y las operaciones sobre la variable manipular ese bloque. Referencing es más común para variables cuyos valores tienen tamaños desconocidos grandes o cuando el código se compilado. Estas variables hacen referencia a la ubicación del valor en lugar del valor almacenar en sí, que es asignado de un grupo de memoria llamada el montón.

Encuadernado las variables tienen valores. Un valor, , sin embargo, es una abstracción, una idea; en implementación, un valor es representado por algún objeto de datos, que se almacena en algún lugar de la memoria de la computadora . El programa, o el entorno de ejecución , debe dejar de lado la memoria para cada objeto de datos y, puesto que la memoria es finita, aseguran que esta memoria es produjo para su reutilización cuando el objeto es ya no sean necesarios para representar valor de algunos de variable.

Objetos asignados del montón deben reclamarse, especialmente cuando los objetos ya no son necesarios.En un lenguaje recolectado de basura (como C#, Java y Lisp), el entorno de tiempo de ejecución recupera automáticamente los objetos cuando las variables existentes no pueden llamarse . En lenguajes no recogidos de basura, como como C, el programa (y el programador) deben asignar memoria explícitamente, y y luego liberarlo para reclamar su memoria . De lo contrario, se producen pérdidas de memoria, en las que el montón se vacía mientras se ejecuta el programa, con el riesgo de fallar la memoria disponible .

Cuando una variable se refiere a un estructura de datos creado dinámicamente, algunos de sus componentes pueden ser sólo indirectamente acceder a través de la variable. En tales circunstancias, los recolectores de basura (o características del programa análogos en lenguas que carecen de basura colectores) deben hacer frente a un caso donde se recuperó sólo una parte de la memoria accesos a las necesidades variables de

+0

¿Cuánto tiempo crees que le tomará a alguien seguir el enlace que has publicado, parafrasear lo que dice con un formato agradable y ganar la respuesta aceptada? –

+1

@Kyle: suena bien. Yo votaría por eso. – Beska

+0

Lo dicen mejor que yo. –

4

Está integrado en el programa.

Básicamente, cuando un programa se compila en lenguaje de máquina, se convierte en una serie de instrucciones. Algunas instrucciones tienen direcciones de memoria incorporadas, y este es el "final de la cadena", por así decirlo. El compilador decide dónde estará cada variable y quema esta información en el archivo ejecutable. (Recuerde que el compilador es un programa diferente al programa que está escribiendo; sólo se concentra en cómo su propio programa funciona por el momento.)

Por ejemplo,

ADD [1A56], 15 

podría añadir 15 al valor en la ubicación 1A56. (Esta instrucción se codificaría utilizando algún código que el procesador entienda, pero no lo explicaré).

Ahora, otras instrucciones le permiten usar una dirección de memoria "variable", una dirección de memoria cargada de algún modo ubicación. Esta es la base de los indicadores en C. Ciertamente no se puede tener una cadena infinita de estos, de lo contrario se quedaría sin memoria.

Espero que aclare las cosas.

1

reducido al metal desnudo, una búsqueda de la variable sea reduce a una dirección que es alguna forma estática desplazamiento conocido a un puntero de base contenida en un registro (el puntero de pila), o que es una dirección constante (variable global).

En un lenguaje interpretado, un registro si a menudo se reserva para mantener un puntero a una estructura de datos (el "entorno") que asocia los nombres de las variables con sus valores actuales.

6

Hay un baile de pasos múltiples que convierte c = 5 en instrucciones de la máquina para actualizar una ubicación en la memoria.

  1. El compilador genera el código en dos partes. Ahí está la parte de instrucciones (cargar un registro con la dirección de C, cargar un registro con el literal 5, almacenar). Y hay una parte de asignación de datos (deje 4 bytes de espacio en el desplazamiento 0 para una variable conocida como "C").

  2. Un "cargador de enlace" tiene que poner esto en la memoria de forma que el sistema operativo pueda ejecutarlo. El cargador solicita memoria y el sistema operativo asigna algunos bloques de memoria virtual. El sistema operativo también asigna la memoria virtual a la memoria física a través de un conjunto de mecanismos de gestión no relacionados.

  3. El cargador coloca la página de datos en un lugar y la parte de instrucciones en otro lugar. Tenga en cuenta que las instrucciones usan direcciones relativas (un desplazamiento de 0 en la página de datos). El cargador proporciona la ubicación real de la página de datos para que las instrucciones puedan resolver la dirección real.

  4. Cuando se ejecuta la instrucción "store" real, el sistema operativo tiene que ver si la página de datos a los que se hace referencia está realmente en la memoria física. Puede estar en el archivo de intercambio y debe cargarse en la memoria física. La dirección virtual que se utiliza se traduce a una dirección física de las ubicaciones de memoria.

+0

Resumen muy bueno y claro. –

3

Voy a expresar mi respuesta en términos muy básicos. Por favor, no te insultéis, solo que no estoy seguro de lo competente que eres y quiero darte una respuesta aceptable para alguien que podría ser un principiante total.

En realidad no está tan lejos en su suposición. El programa al que ejecuta su código, generalmente llamado compilador (o intérprete, según el idioma), realiza un seguimiento de todas las variables que utiliza. Puede pensar en sus variables como una serie de contenedores, y las piezas individuales de datos se mantienen dentro de estos contenedores. Los contenedores tienen etiquetas, y cuando construyes tu código fuente en un programa que puedes ejecutar, todas las etiquetas se transfieren. El compilador se ocupa de esto por usted, de modo que cuando ejecuta el programa, los elementos correctos se obtienen de su contenedor respectivo.

Las variables que utiliza son simplemente otra capa de etiquetas. Esto hace que las cosas sean más fáciles de seguir. La forma en que las variables se almacenan internamente puede tener etiquetas muy complejas o crípticas, pero solo debe preocuparse por cómo se está refiriendo a ellas en su código. Manténgase constante, utilice buenos nombres de variables y realice un seguimiento de lo que está haciendo con sus variables y el compilador/intérprete se encarga de manejar las tareas de bajo nivel asociadas con eso. Este es un caso básico muy simple de uso variable con memoria.

1

En última instancia, los ordenadores solo se activan y desactivan, lo que convenientemente se resume en binario. Este lenguaje es el nivel más bajo y se llama lenguaje de máquina. No estoy seguro de si esto es folclore, pero algunos programadores solían (o quizás aún lo hacen) programar directamente en lenguaje de máquina. Escribir o leer en binario sería muy engorroso, razón por la cual el hexadecimal se usa a menudo para abreviar el binario real.

Como la mayoría de nosotros no somos expertos, el lenguaje de máquina se abstrae en lenguaje ensamblador. Assemply es un lenguaje muy primitivo que controla directamente la memoria. Hay un número muy limitado de comandos (push/pop/add/goto), pero estos finalmente logran todo lo que está programado. Las diferentes arquitecturas de máquina tienen diferentes versiones de ensamblaje, pero la esencia es que hay unas pocas docenas de registros de memoria clave (físicamente en la CPU) - en una arquitectura x86 son EAX, EBX, ECX, EDX, ... Estos contienen datos o punteros que usa la CPU para averiguar qué hacer a continuación. La CPU solo puede hacer 1 cosa a la vez y utiliza estos registros para descubrir qué hacer a continuación. Las computadoras parecen ser capaces de hacer muchas cosas simultáneamente porque la CPU puede procesar estas instrucciones muy rápidamente (millones/mil millones de instrucciones por segundo). Por supuesto, los procesadores multi-core complican las cosas, pero no vayamos allí ...

Como la mayoría de nosotros no somos lo suficientemente inteligentes o precisos para programar en ensamblaje donde se puede colgar fácilmente el sistema, el ensamblaje se abstrae en una Lenguaje de tercera generación (3GL): este es su C/C++/C#/Java, etc.Cuando le dice a uno de estos idiomas que ponga el valor entero 5 en una variable, sus instrucciones se almacenan en texto; el ensamblador compila su texto en un archivo de ensamblaje (ejecutable); cuando se ejecuta el programa, la CPU pone en cola el programa y sus instrucciones, cuando se muestra la hora para esa línea de código específica, se lee en el registro de la CPU y se procesa.

Los comentarios "no lo suficientemente inteligentes" sobre los idiomas son un poco irónicos. Teóricamente, cuanto más se aleje de ceros y unos para lenguaje humano simple, más rápido y eficientemente podrá producir código.

+0

Para enseñarme a mí mismo el ensamblaje en la edad de piedra, codifiqué manualmente los códigos binarios (en un 6502), luego los "pinché" desde Basic-in-ROM. Y en los años 80, donde trabajé, a veces tuve que arrancar algunas máquinas viejas de HP al alternar una serie de 16 interruptores para cargar manualmente los registros de la CPU, y luego presionar el botón "ejecutar". Afortunadamente, eso fue solo para un modo de mantenimiento. – NVRAM

1

Aquí hay algunas fallas importantes que supone que todas las variables se almacenan en la memoria. Bueno, a menos que cuentes los registros de la CPU como memoria, entonces esto no será del todo correcto. Algunos compiladores optimizarán el código generado y si pueden mantener una variable almacenada en un registro, ¡algunos compiladores harán uso de esto! Luego, por supuesto, está la compleja cuestión del montón y la memoria de la pila. ¡Las variables locales se pueden ubicar en ambos! La ubicación preferida estaría en la pila, a la que se accede con más frecuencia que en el montón. Este es el caso de casi todas las variables locales. Las variables globales a menudo son parte del segmento de datos del ejecutable final y tienden a formar parte del heap, aunque no se pueden liberar estas áreas de memoria global. Pero el montón se usa a menudo para asignaciones sobre la marcha de nuevos bloques de memoria, alloc memoria de acceso para ellos.

Pero con variables globales, el código sabrá exactamente dónde están y por lo tanto escriben su ubicación exacta en el código. (Bueno, su ubicación desde el principio del segmento de datos de todos modos.) Las variables de registro se encuentran en la CPU y el compilador sabe exactamente qué registro, que también se le acaba de decir al código. Las variables de pila están ubicadas en un desplazamiento desde el puntero de pila actual. Este puntero de pila aumentará y disminuirá todo el tiempo, según la cantidad de niveles de procedimientos que invoquen otros procedimientos. Solo los valores de montón son complejos. Cuando la aplicación necesita almacenar datos en el montón, necesita una segunda variable para almacenar su dirección, de lo contrario podría perder la pista. Esta segunda variable se llama puntero y se ubica como datos globales o como parte de la pila. (O, en raras ocasiones, en los registros de la CPU.)

Oh, es incluso un poco más complejo que esto, pero ya puedo ver algunos ojos rodando debido a esta información exagerada. :-)

1

Piense en la memoria como un cajón en el que decide cómo distribuirlo según sus necesidades espontáneas.

Cuando declara una variable de tipo entero o de cualquier otro tipo, el compilador o intérprete (lo que sea) asigna una dirección de memoria en su segmento de datos (registro DS en ensamblador) y reserva una cierta cantidad de direcciones siguientes según su tipo longitud en bit

Según su pregunta, un número entero tiene 32 bits de longitud, por lo tanto, desde una dirección determinada, digamos D003F8AC, los 32 bits que siguen a esta dirección se reservarán para su entero declarado.

En tiempo de compilación, donde quiera que haga referencia a su variable, el código ensamblador generado lo reemplazará por su dirección DS. Entonces, cuando obtiene el valor de su variable C, el procesador consulta la dirección D003F8AC y la recupera.

Espero que esto ayude, ya que usted tiene muchas respuestas. :-)

Cuestiones relacionadas