programar - ¿Cómo los compiladores asignan direcciones de memoria a las variables?
lenguaje c pdf (4)
Creo que la respuesta a esta pregunta comienza con una comprensión del diseño de un programa en la memoria. Debajo del sistema operativo, la memoria principal de una computadora es solo una matriz gigante. Cuando ejecute un programa, el sistema operativo tomará una porción de esta memoria y la dividirá en secciones lógicas para los siguientes propósitos:
pila: esta área de la memoria almacena información sobre todas las funciones actualmente en alcance, incluida la función actualmente en ejecución y todos sus ancestros. La información almacenada incluye variables locales y la dirección a la que volver cuando se realiza la función.
montón: esta área de memoria se usa cuando desea asignar dinámicamente algo de almacenamiento. En general, su variable local contendría una dirección (es decir, sería un puntero) en el montón donde se almacenan sus datos, y puede publicar esta dirección en otras partes de su programa sin preocuparse de que sus datos se sobrescriban cuando se actualice La función queda fuera de alcance.
data, bss, segmentos de texto: están más o menos fuera del alcance de esta pregunta en particular, pero almacenan cosas como los datos globales y el programa en sí.
Espero que ayude. Hay muchos buenos recursos en línea también. Acabo de buscar en Google "diseño de un programa en memoria" y encontré este: http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory
Enseño un curso donde los estudiantes pueden hacer preguntas sobre la programación (!): Tengo esta pregunta:
¿Por qué la máquina elige si las variables van en la memoria? ¿Podemos decirle dónde almacenar una variable?
Realmente no sé qué decir. Aquí está mi primer intento:
El compilador (no la máquina) elige dónde almacenar las variables en el espacio de direcciones del proceso automáticamente. Usando C, no podemos decirle a la máquina dónde almacenar las variables.
Pero eso "automáticamente" es un tanto anticlimático y plantea la pregunta ... y me di cuenta de que ni siquiera sé si es el compilador, el tiempo de ejecución o el sistema operativo o quién realiza la tarea. Tal vez alguien pueda responder la pregunta del estudiante mejor que yo.
La respuesta a esta pregunta es bastante compleja ya que existen varios enfoques para la asignación de memoria dependiendo del alcance variable, el tamaño y el entorno de programación.
Apilar variables asignadas
Normalmente local variables
se ponen en la "pila". Esto significa que el compilador asigna un desplazamiento al "puntero de pila" que puede ser diferente dependiendo de la invocación de la función actual. Es decir, el compilador asume que las ubicaciones de memoria como Stack-Pointer + 4, Stack-Pointer + 8, etc. son accesibles y utilizables por el programa. Al return
de la función, no se garantiza que las ubicaciones de la memoria retengan estos valores.
Esto se asigna en instrucciones de montaje similares a las siguientes. esp
es el puntero de pila, esp + N
refiere a una ubicación de memoria relativa a esp:
mov eax, DWORD PTR SS:[esp]
mov eax, DWORD PTR SS:[esp + 4]
mov eax, DWORD PTR SS:[esp + 8]
Montón
Luego hay variables que son asignadas en montón. Esto significa que hay una llamada a la biblioteca para solicitar memoria de la biblioteca estándar ( alloc
en C o new
en C ++). Esta memoria está reservada hasta el final de la ejecución de los programas. alloc
y new
punteros de retorno a la memoria en una región de la memoria llamada el montón. Las funciones de asignación deben asegurarse de que la memoria no esté reservada, lo que a veces puede hacer que la asignación de pilas sea lenta. Además, si no desea quedarse sin memoria, debe free
(o delete
) la memoria que ya no se utiliza. La asignación de montones es bastante complicada internamente, ya que la biblioteca estándar debe realizar un seguimiento de los rangos utilizados y no utilizados en la memoria, así como los rangos de memoria liberados. Por lo tanto, incluso liberar una variable asignada a un montón puede llevar más tiempo que asignarla. Para obtener más información, consulte ¿Cómo se implementa internamente malloc ()?
Comprender la diferencia entre la pila y el montón es bastante fundamental para aprender a programar en C y C ++.
Punteros arbitrarios
De manera ingenua, se podría suponer que al establecer un puntero a una dirección arbitraria int *a = 0x123
debería ser posible direccionar ubicaciones arbitrarias en la memoria de la computadora. Esto no es exactamente cierto ya que (dependiendo de la CPU y el sistema) los programas están muy restringidos cuando se trata de la memoria.
Conseguir una sensación de memoria
En una experiencia de clase guiada, podría ser beneficioso explorar algunos códigos C simples compilando el código fuente al ensamblador (por ejemplo, gcc puede hacer esto). Una función simple como int foo(int a, int b) { return a+b;}
debería ser suficiente (sin optimizaciones). Luego vea algo como int bar(int *a, int *b) { return (*a) + (*b);}
;
Cuando invoque la barra, asigne los parámetros una vez en la pila, una vez por malloc.
Conclusión
El compilador realiza algunas colocaciones y alineaciones variables en relación con las direcciones base que se obtienen por el programa / biblioteca estándar en tiempo de ejecución.
Para una comprensión más profunda de las preguntas relacionadas con la memoria, vea lo que Ulrich Drepper "Lo que todo programador debería saber sobre la memoria" http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.91.957
Aparte de idenote país C-ish
Luego también está la recolección de basura, que es popular entre muchos lenguajes de scripting (Python, Perl, Javascript, lisp) y entornos independientes del dispositivo (Java, C #). Está relacionado con la asignación del montón, pero un poco más complicado.
Las variedades de lenguajes de programación solo están basadas en pila (python sin pila) o completamente en pila (en adelante).
Las variables locales dentro de una función normalmente se distribuirán de forma secuencial dentro del marco de pila de la función.
Me gustaría añadir algunos. Para el firmware donde conoce el mapa de memoria y la dirección de ejecución, y donde compila la fuente utilizando su propio script de vinculador-
Puede asignar una sección personalizada a una variable usando el atributo de sección y luego asignar una dirección particular a la sección personalizada a través del script del vinculador. Entonces la variable obtendría una dirección fija / asignada.