Para comprender mejor lo que está sucediendo, imaginemos que solo tenemos un sistema operativo muy primitivo que se ejecuta en un procesador de 16 bits que puede ejecutar solo un proceso a la vez. Es decir: solo se puede ejecutar un programa a la vez. Además, imaginemos que todas las interrupciones están deshabilitadas.
Hay un constructo en nuestro procesador llamado pila. La pila es una construcción lógica impuesta en la memoria física. Digamos que nuestra RAM existe en las direcciones E000 a FFFF. Esto significa que nuestro programa en ejecución puede usar esta memoria como queramos. Imaginemos que nuestro sistema operativo dice que E000 a EFFF es la pila, y F000 a FFFF es el montón.
La pila se mantiene mediante el hardware y las instrucciones de la máquina. Realmente no hay mucho que hacer para mantenerlo. Todo lo que (o nuestro sistema operativo) debemos hacer es asegurarnos de establecer una dirección adecuada para el inicio de la pila. El puntero de la pila es una entidad física que reside en el hardware (procesador) y se gestiona mediante instrucciones del procesador. En este caso, nuestro puntero de pila se establecerá en EFFF (suponiendo que la pila crezca HACIA ATRÁS, que es bastante común, -). Con un lenguaje compilado como C, cuando llamas a una función, empuja cualquier argumento que hayas pasado a la función en la pila. Cada argumento tiene un cierto tamaño. int es usualmente 16 o 32 bits, char es usualmente 8 bits, etc. Imaginemos que en nuestro sistema, int e int * son 16 bits. Para cada argumento, el puntero de la pila es DISMINUIDO (-) por sizeof (argumento), y el argumento se copia en la pila. Entonces, cualquier variable que haya declarado en el alcance se inserta en la pila de la misma manera, pero sus valores no se inicializan.
Consideremos dos ejemplos similares a los dos ejemplos.
int hello(int eeep)
{
int i;
int *p;
}
¿Qué sucede aquí en nuestro sistema de 16 bits es la siguiente: 1) empuje eeep en la pila. Esto significa que disminuimos el puntero de pila a EFFD (porque sizeof (int) es 2) y luego copiamos eeep para direccionar EFFE (el valor actual de nuestro puntero de pila, menos 1 porque nuestro puntero de pila apunta al primer punto que está disponible después de la asignación). A veces hay instrucciones que pueden hacer ambas cosas de una sola vez (suponiendo que está copiando datos que encajan en un registro. De lo contrario, tendría que copiar manualmente cada elemento de un tipo de datos en su lugar correcto en la pila, ¡el orden es importante!)
2) crear espacio para i. Esto probablemente significa simplemente disminuir el puntero de la pila a EFFB.
3) crear espacio para p. Esto probablemente significa simplemente disminuir el puntero de la pila a EFF9.
Luego se ejecuta nuestro programa, recordando dónde viven nuestras variables (eeep comienza en EFFE, i en EFFC, y p en EFFA). Lo importante que hay que recordar es que, aunque la pila cuenta BACKWARDS, las variables aún funcionan FORWARDS (esto depende de endianness, pero el punto es que & eeep == EFFE, no EFFF).
Cuando la función se cierra, simplemente incremento (++) el puntero de pila en un 6, (porque 3 "objetos", no el C++ tipo, de tamaño 2 han sido empujados a la pila.
Ahora, el segundo escenario es mucho más difícil de explicar porque hay muchos métodos para llevarla a cabo que es casi imposible de explicar en el Internet.
int hello(int eeep)
{
int *p = malloc(sizeof(int));//C's pseudo-equivalent of new
free(p);//C's pseudo-equivalent of delete
}
eeep yp todavía se empujan y se asignan en la pila como en el anterior ejemplo. En este caso, sin embargo, inicializamos p al resultado de una llamada a función. ¿Qué malloc (o nuevo, pero nuevo hace más en C++) llama a los contras? tructors cuando sea apropiado, y todo lo demás.) lo hace se va a esta caja negra llamada HEAP y obtiene una dirección de memoria libre. Nuestro sistema operativo administrará el montón para nosotros, pero tenemos que dejarlo saber cuando queremos memoria y cuando hayamos terminado con ella.
En el ejemplo, cuando llamamos a malloc(), el sistema operativo devolverá un bloque de 2 bytes (sizeof (int) en nuestro sistema es 2) al proporcionarnos la dirección de inicio de estos bytes. Digamos que la primera llamada nos dio la dirección F000. El sistema operativo entonces realiza un seguimiento de que las direcciones F000 y F001 están actualmente en uso. Cuando llamamos a free (p), el sistema operativo encuentra el bloque de memoria que p apunta y marca 2 bytes como no utilizado (porque sizeof (estrella p) es 2). Si, en cambio, asignamos más memoria, la dirección F002 probablemente se devolverá como el bloque de inicio de la nueva memoria. Tenga en cuenta que malloc() en sí mismo es una función. Cuando p se inserta en la pila para la llamada de malloc(), la p se copia en la pila nuevamente en la primera dirección abierta que tiene suficiente espacio en la pila para ajustarse al tamaño de p (probablemente EFFB, porque solo presionamos 2 cosas en la pila esta vez de tamaño 2, y sizeof (p) es 2), y el puntero de la pila se decrementa nuevamente a EFF9, y malloc() colocará sus variables locales en la pila comenzando en esta ubicación. Cuando Malloc termina, saca todos sus elementos de la pila y establece el puntero de la pila como era antes de que se llamara. El valor de retorno de malloc(), una estrella vacía, probablemente se colocará en algún registro (generalmente el acumulador en muchos sistemas) para nuestro uso.
En la implementación, ambos ejemplos REALMENTE no son así de simples. Cuando asigna memoria de pila, para una nueva llamada de función, debe asegurarse de guardar su estado (guardar todos los registros) para que la nueva función no borre los valores de forma permanente. Esto generalmente implica empujarlos en la pila, también. De la misma manera, generalmente guardará el registro del contador del programa para que pueda regresar al lugar correcto después de que la subrutina regrese. Los administradores de memoria usan memoria propia para "recordar" qué memoria se ha entregado y qué no. La memoria virtual y la segmentación de la memoria complican aún más este proceso, y los algoritmos de administración de memoria deben mover bloques continuamente (y protegerlos también) para evitar la fragmentación de la memoria (un tema completo), y esto se relaciona con la memoria virtual también. El segundo ejemplo realmente es una gran lata de gusanos en comparación con el primer ejemplo. Además, ejecutar múltiples procesos hace que todo esto sea mucho más complicado, ya que cada proceso tiene su propia pila, y se puede acceder al montón mediante más de un proceso (lo que significa que debe protegerse). Además, cada arquitectura de procesador es diferente. Algunas arquitecturas esperarán que configure el puntero de la pila en la primera dirección libre en la pila, mientras que otros esperarán que lo apunte al primer lugar no libre.
Espero que esto haya ayudado. Por favor hagamelo saber.
aviso, todos los ejemplos anteriores son para una máquina ficticia que está demasiado simplificada. En hardware real, esto se pone un poco más peludo.
editar: los asteriscos no aparecen. i los reemplazó con la palabra "estrella"
Por lo que vale la pena, si utilizamos (en su mayoría) el mismo código en los ejemplos, en sustitución de "hola" con "ejemplo1" y "example2", respectivamente, se obtener el siguiente resultado de ensamblaje para intel on wndows.
.file "test1.c"
.text
.globl _example1
.def _example1; .scl 2; .type 32; .endef
_example1:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
leave
ret
.globl _example2
.def _example2; .scl 2; .type 32; .endef
_example2:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $4, (%esp)
call _malloc
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
movl %eax, (%esp)
call _free
leave
ret
.def _free; .scl 3; .type 32; .endef
.def _malloc; .scl 3; .type 32; .endef
¿Cómo pueden tener la misma asignación de memoria? El primero asigna un tamaño para un int (en la pila) y un puntero (en la pila). El segundo asigna un puntero en la pila y espacio para un int en el montón. – bobbyalex
Básicamente mi pregunta es "En tiempo de ejecución, ¿cuál es la diferencia entre la pila y el montón?" – Tarquila
Pruebe aquí: http://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap – Alex