Los libros de lenguaje de programación explican que los tipos de valor se crean en la pila y los tipos de referencia se crean en el montón , sin explicar cuáles son estas dos cosas. No he leído una explicación clara de esto. Entiendo lo que es una pila . Pero,
- ¿Dónde y qué están (físicamente en la memoria de una computadora real)?
- ¿En qué medida están controlados por el sistema operativo o el lenguaje en tiempo de ejecución?
- ¿Cuál es su alcance?
- ¿Qué determina el tamaño de cada uno de ellos?
- ¿Qué lo hace a uno más rápido?
rlimit_stack
. También vea Red Hat Issue 1463241Respuestas:
La pila es la memoria reservada como espacio reutilizable para un hilo de ejecución. Cuando se llama a una función, se reserva un bloque en la parte superior de la pila para variables locales y algunos datos de contabilidad. Cuando esa función regresa, el bloque no se usa y se puede usar la próxima vez que se llame a una función. La pila siempre está reservada en un orden LIFO (último en entrar, primero en salir); el bloque reservado más recientemente es siempre el siguiente bloque que se liberará. Esto hace que sea realmente sencillo hacer un seguimiento de la pila; liberar un bloque de la pila no es más que ajustar un puntero.
El montón es la memoria reservada para la asignación dinámica. A diferencia de la pila, no hay un patrón obligatorio para la asignación y desasignación de bloques del montón; Puede asignar un bloque en cualquier momento y liberarlo en cualquier momento. Esto hace que sea mucho más complejo hacer un seguimiento de qué partes del montón están asignadas o libres en un momento dado; Hay muchos asignadores de almacenamiento dinámico disponibles para ajustar el rendimiento del almacenamiento dinámico para diferentes patrones de uso.
Cada subproceso obtiene una pila, mientras que normalmente solo hay un montón para la aplicación (aunque no es raro tener varios montones para diferentes tipos de asignación).
Para responder sus preguntas directamente:
El sistema operativo asigna la pila para cada subproceso de nivel de sistema cuando se crea el subproceso. Normalmente, el tiempo de ejecución del lenguaje llama al sistema operativo para asignar el montón a la aplicación.
La pila está unida a un hilo, por lo que cuando el hilo sale, la pila se recupera. El montón generalmente se asigna al inicio de la aplicación por el tiempo de ejecución, y se recupera cuando la aplicación (técnicamente proceso) se cierra.
El tamaño de la pila se establece cuando se crea un hilo. El tamaño del montón se establece en el inicio de la aplicación, pero puede crecer a medida que se necesita espacio (el asignador solicita más memoria del sistema operativo).
La pila es más rápida porque el patrón de acceso hace que sea trivial asignar y desasignar memoria de ella (un puntero / entero simplemente se incrementa o disminuye), mientras que el montón tiene una contabilidad mucho más compleja involucrada en una asignación o desasignación. Además, cada byte en la pila tiende a reutilizarse con mucha frecuencia, lo que significa que tiende a asignarse a la memoria caché del procesador, lo que lo hace muy rápido. Otro golpe de rendimiento para el montón es que el montón, que es principalmente un recurso global, generalmente tiene que ser seguro para múltiples subprocesos, es decir, cada asignación y desasignación debe estar, típicamente, sincronizada con "todos" otros accesos de montón en el programa.
Una demostración clara:
Fuente de la imagen: vikashazrati.wordpress.com
fuente
Apilar:
Montón:
delete
,delete[]
ofree
.new
omalloc
respectivamente.Ejemplo:
fuente
C
idioma, tal como lo define elC99
estándar del idioma (disponible en open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf ), requiere una "pila". De hecho, la palabra 'apilar' ni siquiera aparece en el estándar. Esto responde que elC
uso de la pila de wrt / to es verdadero en general, pero de ninguna manera es requerido por el lenguaje. Consulte knosof.co.uk/cbook/cbook.html para obtener más información y, en particular, cómoC
se implementa en arquitecturas de bolas extrañas como en.wikipedia.org/wiki/Burroughs_large_systemsEl punto más importante es que montón y pila son términos genéricos para las formas en que se puede asignar memoria. Se pueden implementar de muchas maneras diferentes, y los términos se aplican a los conceptos básicos.
En una pila de elementos, los elementos se colocan uno encima del otro en el orden en que se colocaron allí, y solo puede quitar el superior (sin volcar todo el objeto).
La simplicidad de una pila es que no necesita mantener una tabla que contenga un registro de cada sección de memoria asignada; la única información de estado que necesita es un puntero único al final de la pila. Para asignar y desasignar, simplemente incremente y disminuya ese puntero único. Nota: a veces se puede implementar una pila para comenzar en la parte superior de una sección de memoria y extenderse hacia abajo en lugar de crecer hacia arriba.
En un montón, no hay un orden particular en la forma en que se colocan los elementos. Puede acceder y eliminar elementos en cualquier orden porque no hay un elemento 'superior' claro.
La asignación del montón requiere mantener un registro completo de qué memoria está asignada y qué no, así como un poco de mantenimiento general para reducir la fragmentación, encontrar segmentos de memoria contiguos lo suficientemente grandes como para ajustarse al tamaño solicitado, y así sucesivamente. La memoria se puede desasignar en cualquier momento dejando espacio libre. A veces, un asignador de memoria realizará tareas de mantenimiento, como desfragmentar la memoria moviendo la memoria asignada o recolectando basura, identificando en tiempo de ejecución cuando la memoria ya no está dentro del alcance y desasignándola.
Estas imágenes deberían hacer un trabajo bastante bueno al describir las dos formas de asignar y liberar memoria en una pila y un montón. Ñam!
¿En qué medida están controlados por el sistema operativo o el tiempo de ejecución del lenguaje?
Como se mencionó, heap y stack son términos generales, y se pueden implementar de muchas maneras. Los programas de ordenador tienen típicamente una pila llamada pila de llamadas , que almacena la información relevante para la función actual como un puntero a cualquier función que fue llamado, y cualquier variable local. Debido a que las funciones llaman a otras funciones y luego regresan, la pila crece y se reduce para retener información de las funciones más abajo en la pila de llamadas. Un programa realmente no tiene control de tiempo de ejecución sobre él; está determinado por el lenguaje de programación, el sistema operativo e incluso la arquitectura del sistema.
Un montón es un término general utilizado para cualquier memoria que se asigne de forma dinámica y aleatoria; es decir, fuera de servicio. El sistema operativo suele asignar la memoria, y la aplicación llama a las funciones API para hacer esta asignación. Se requiere un poco de sobrecarga para administrar la memoria asignada dinámicamente, que generalmente se maneja mediante el código de tiempo de ejecución del lenguaje de programación o el entorno utilizado.
¿Cuál es su alcance?
La pila de llamadas es un concepto de nivel tan bajo que no se relaciona con el 'alcance' en el sentido de la programación. Si desarma algún código, verá referencias de estilo de puntero relativas a partes de la pila, pero en lo que respecta a un lenguaje de nivel superior, el idioma impone sus propias reglas de alcance. Sin embargo, un aspecto importante de una pila es que una vez que una función regresa, cualquier cosa local a esa función se libera inmediatamente de la pila. Eso funciona de la manera que esperarías que funcione dado el funcionamiento de tus lenguajes de programación. En un montón, también es difícil de definir. El alcance es lo que expone el sistema operativo, pero su lenguaje de programación probablemente agrega sus reglas sobre qué es un "alcance" en su aplicación. La arquitectura del procesador y el sistema operativo utilizan direccionamiento virtual, que el procesador traduce a direcciones físicas y hay fallas de página, etc. Realizan un seguimiento de qué páginas pertenecen a qué aplicaciones. Sin embargo, nunca debe preocuparse realmente por esto, ya que solo usa cualquier método que use su lenguaje de programación para asignar y liberar memoria, y verificar si hay errores (si la asignación / liberación falla por algún motivo).
¿Qué determina el tamaño de cada uno de ellos?
Nuevamente, depende del idioma, el compilador, el sistema operativo y la arquitectura. Una pila suele estar preasignada, porque por definición debe ser memoria contigua. El compilador de idiomas o el sistema operativo determinan su tamaño. No almacena grandes cantidades de datos en la pila, por lo que será lo suficientemente grande como para que nunca se use por completo, excepto en casos de recursión interminable no deseada (por lo tanto, "desbordamiento de pila") u otras decisiones de programación inusuales.
Un montón es un término general para cualquier cosa que pueda asignarse dinámicamente. Dependiendo de la forma en que lo mires, cambia constantemente de tamaño. De todos modos, en los procesadores y sistemas operativos modernos, la forma exacta en que funciona es muy abstracta, por lo que normalmente no debe preocuparse mucho por cómo funciona en el fondo, excepto que (en los idiomas que le permite) no debe usar memoria que aún no ha asignado o memoria que ha liberado.
¿Qué lo hace a uno más rápido?
La pila es más rápida porque toda la memoria libre siempre es contigua. No es necesario mantener una lista de todos los segmentos de memoria libre, solo un puntero a la parte superior actual de la pila. Los compiladores generalmente almacenan este puntero en un registro especial y rápido para este propósito. Además, las operaciones posteriores en una pila generalmente se concentran en áreas de memoria muy cercanas, lo que a un nivel muy bajo es bueno para la optimización de las memorias caché en el procesador.
fuente
(He movido esta respuesta de otra pregunta que fue más o menos un engaño de esta).
La respuesta a su pregunta es específica de la implementación y puede variar entre compiladores y arquitecturas de procesador. Sin embargo, aquí hay una explicación simplificada.
El montón
new
omalloc
) se satisfacen creando un bloque adecuado a partir de uno de los bloques libres. Esto requiere actualizar la lista de bloques en el montón. Esta metainformación sobre los bloques en el montón también se almacena en el montón a menudo en un área pequeña justo delante de cada bloque.La pila
No, los registros de activación para funciones (es decir, variables locales o automáticas) se asignan en la pila que se utiliza no solo para almacenar estas variables, sino también para realizar un seguimiento de las llamadas a funciones anidadas.
Cómo se gestiona el montón depende realmente del entorno de tiempo de ejecución. C usa
malloc
y C ++ usanew
, pero muchos otros lenguajes tienen recolección de basura.Sin embargo, la pila es una característica de nivel más bajo estrechamente vinculada a la arquitectura del procesador. Hacer crecer el montón cuando no hay suficiente espacio no es demasiado difícil, ya que se puede implementar en la llamada a la biblioteca que maneja el montón. Sin embargo, el crecimiento de la pila a menudo es imposible ya que el desbordamiento de la pila solo se descubre cuando es demasiado tarde; y cerrar el hilo de ejecución es la única opción viable.
fuente
En el siguiente código C #
Así se gestiona la memoria
Local Variables
eso solo necesita durar mientras la invocación de la función vaya en la pila. El montón se usa para variables cuya vida útil realmente no conocemos por adelantado, pero esperamos que duren un tiempo. En la mayoría de los idiomas, es fundamental que sepamos en tiempo de compilación qué tan grande es una variable si queremos almacenarla en la pila.Los objetos (que varían en tamaño a medida que los actualizamos) van al montón porque no sabemos en el momento de la creación cuánto tiempo durarán. En muchos idiomas, el montón es basura recolectada para encontrar objetos (como el objeto cls1) que ya no tienen referencias.
En Java, la mayoría de los objetos van directamente al montón. En lenguajes como C / C ++, las estructuras y las clases a menudo pueden permanecer en la pila cuando no se trata de punteros.
Más información se puede encontrar aquí:
La diferencia entre la asignación de la pila y la memoria del montón «timmurphy.org
y aquí:
Crear objetos en la pila y el montón
Este artículo es la fuente de la imagen de arriba: Seis conceptos importantes de .NET: pila, montón, tipos de valores, tipos de referencia, boxeo y unboxing - CodeProject
pero tenga en cuenta que puede contener algunas imprecisiones.
fuente
La pila Cuando llama a una función, los argumentos de esa función más alguna otra sobrecarga se colocan en la pila. Alguna información (como a dónde ir al regreso) también se almacena allí. Cuando declara una variable dentro de su función, esa variable también se asigna en la pila.
Desasignar la pila es bastante simple porque siempre desasigna en el orden inverso en el que asigna. Las cosas de la pila se agregan a medida que ingresa las funciones, los datos correspondientes se eliminan al salir de ellas. Esto significa que tiende a permanecer dentro de una pequeña región de la pila a menos que llame a muchas funciones que invocan muchas otras funciones (o cree una solución recursiva).
El montón El montón es un nombre genérico para el lugar donde coloca los datos que crea sobre la marcha. Si no sabe cuántas naves espaciales creará su programa, es probable que use el nuevo operador (o malloc o equivalente) para crear cada nave espacial. Esta asignación se mantendrá durante un tiempo, por lo que es probable que liberemos las cosas en un orden diferente al que las creamos.
Por lo tanto, el montón es mucho más complejo, porque terminan siendo regiones de memoria que no se utilizan entrelazadas con fragmentos que sí lo son: la memoria se fragmenta. Encontrar memoria libre del tamaño que necesita es un problema difícil. Es por eso que se debe evitar el montón (aunque todavía se usa con frecuencia).
Implementación La implementación de la pila y el montón generalmente depende del tiempo de ejecución / SO. A menudo, los juegos y otras aplicaciones que son críticas para el rendimiento crean sus propias soluciones de memoria que toman una gran cantidad de memoria del montón y luego la distribuyen internamente para evitar depender del sistema operativo para la memoria.
Esto solo es práctico si su uso de memoria es bastante diferente de la norma, es decir, para juegos en los que carga un nivel en una operación enorme y puede tirar todo el lote en otra operación enorme.
Ubicación física en la memoria Esto es menos relevante de lo que cree debido a una tecnología llamada Memoria virtual que hace que su programa piense que tiene acceso a una determinada dirección donde los datos físicos están en otro lugar (¡incluso en el disco duro!). Las direcciones que obtiene para la pila están en orden creciente a medida que su árbol de llamadas se vuelve más profundo. Las direcciones para el montón no son predecibles (es decir, específicas de la implementación) y, francamente, no son importantes.
fuente
Para aclarar, esta respuesta tiene información incorrecta ( Thomas arregló su respuesta después de los comentarios, genial :)). Otras respuestas simplemente evitan explicar qué significa la asignación estática. Así que explicaré las tres formas principales de asignación y cómo se relacionan generalmente con el segmento de montón, pila y datos a continuación. También mostraré algunos ejemplos en C / C ++ y Python para ayudar a las personas a comprender.
Las variables "estáticas" (AKA asignadas estáticamente) no se asignan en la pila. No asuma que muchas personas lo hacen solo porque "estático" se parece mucho a "stack". En realidad no existen ni en la pila ni en el montón. Son parte de lo que se llama segmento de datos .
Sin embargo, generalmente es mejor considerar " alcance " y " duración " en lugar de "apilar" y "montón".
El alcance se refiere a qué partes del código pueden acceder a una variable. Generalmente pensamos en el alcance local (solo se puede acceder mediante la función actual) versus el alcance global (se puede acceder desde cualquier lugar), aunque el alcance puede ser mucho más complejo.
La duración se refiere a cuando una variable se asigna y se desasigna durante la ejecución del programa. Por lo general, pensamos en la asignación estática (la variable persistirá durante toda la duración del programa, por lo que es útil para almacenar la misma información en varias llamadas a funciones) frente a la asignación automática (la variable solo persiste durante una sola llamada a una función, por lo que es útil para almacenar información que solo se usa durante su función y puede descartarse una vez que haya terminado) versus asignación dinámica (variables cuya duración se define en tiempo de ejecución, en lugar de tiempo de compilación como estático o automático).
Aunque la mayoría de los compiladores e intérpretes implementan este comportamiento de manera similar en términos de uso de pilas, montones, etc., un compilador a veces puede romper estas convenciones si lo desea, siempre y cuando el comportamiento sea correcto. Por ejemplo, debido a la optimización, una variable local solo puede existir en un registro o eliminarse por completo, aunque la mayoría de las variables locales existen en la pila. Como se ha señalado en algunos comentarios, puede implementar un compilador que ni siquiera usa una pila o un montón, sino algunos otros mecanismos de almacenamiento (rara vez se hace, ya que las pilas y los montones son excelentes para esto).
Proporcionaré un código C simple anotado para ilustrar todo esto. La mejor manera de aprender es ejecutar un programa bajo un depurador y observar el comportamiento. Si prefiere leer Python, salte al final de la respuesta :)
Un ejemplo particularmente conmovedor de por qué es importante distinguir entre la duración y el alcance es que una variable puede tener un alcance local pero una duración estática, por ejemplo, "someLocalStaticVariable" en el ejemplo de código anterior. Dichas variables pueden hacer que nuestros hábitos de denominación comunes pero informales sean muy confusos. Por ejemplo, cuando decimos " local " usualmente queremos decir " variable asignada localmente con alcance local " y cuando decimos global generalmente queremos decir " variable asignada estáticamente con alcance global ". Desafortunadamente cuando se trata de cosas como " variables asignadas estáticamente a archivos con alcance ", muchas personas simplemente dicen ... " ¿eh? ".
Algunas de las opciones de sintaxis en C / C ++ exacerban este problema; por ejemplo, muchas personas piensan que las variables globales no son "estáticas" debido a la sintaxis que se muestra a continuación.
Tenga en cuenta que poner la palabra clave "static" en la declaración anterior evita que var2 tenga un alcance global. Sin embargo, la var1 global tiene asignación estática. ¡Esto no es intuitivo! Por esta razón, trato de nunca usar la palabra "estática" cuando describo el alcance, y en su lugar digo algo como "archivo" o "archivo limitado". Sin embargo, muchas personas usan la frase "estática" o "alcance estático" para describir una variable a la que solo se puede acceder desde un archivo de código. En el contexto de la vida útil, "estático" siempre significa que la variable se asigna al inicio del programa y se desasigna cuando el programa sale.
Algunas personas piensan que estos conceptos son específicos de C / C ++. No son. Por ejemplo, el ejemplo de Python a continuación ilustra los tres tipos de asignación (hay algunas diferencias sutiles posibles en los lenguajes interpretados en los que no entraré aquí).
fuente
PostScript
tienen múltiples pilas, pero tienen un "montón" que se comporta más como una pila.Otros han respondido bastante bien a los trazos amplios, así que agregaré algunos detalles.
La pila y el montón no necesitan ser singulares. Una situación común en la que tiene más de una pila es si tiene más de un hilo en un proceso. En este caso, cada hilo tiene su propia pila. También puede tener más de un montón, por ejemplo, algunas configuraciones de DLL pueden dar lugar a diferentes DLL que se asignan desde diferentes montones, por lo que generalmente es una mala idea liberar memoria asignada por una biblioteca diferente.
En C puede obtener el beneficio de la asignación de longitud variable mediante el uso de alloca , que asigna en la pila, en lugar de alloc, que asigna en el montón. Esta memoria no sobrevivirá a su declaración de devolución, pero es útil para un buffer de memoria virtual.
Hacer un gran búfer temporal en Windows que no utilizas mucho no es gratis. Esto se debe a que el compilador generará un bucle de sonda de pila que se llama cada vez que se ingresa su función para asegurarse de que la pila existe (porque Windows usa una sola página de protección al final de su pila para detectar cuándo necesita aumentarla. Si accede a la memoria a más de una página del final de la pila, se bloqueará). Ejemplo:
fuente
alloca
?Otros han respondido directamente a su pregunta, pero al tratar de entender la pila y el montón, creo que es útil considerar el diseño de memoria de un proceso UNIX tradicional (sin hilos y
mmap()
asignadores basados). La página web del Glosario de administración de memoria tiene un diagrama de este diseño de memoria.La pila y el montón se ubican tradicionalmente en los extremos opuestos del espacio de direcciones virtuales del proceso. La pila crece automáticamente cuando se accede, hasta un tamaño establecido por el núcleo (que se puede ajustar con
setrlimit(RLIMIT_STACK, ...)
). El montón crece cuando el asignador de memoria invoca elbrk()
osbrk()
llamada al sistema, la cartografía más páginas de memoria física en el espacio virtual de direcciones del proceso.En sistemas sin memoria virtual, como algunos sistemas integrados, a menudo se aplica el mismo diseño básico, excepto que la pila y el montón son de tamaño fijo. Sin embargo, en otros sistemas integrados (como los basados en microcontroladores PIC de Microchip), la pila del programa es un bloque de memoria separado que no es direccionable por las instrucciones de movimiento de datos, y solo puede modificarse o leerse indirectamente a través de las instrucciones de flujo del programa (llamada, volver, etc.). Otras arquitecturas, como los procesadores Intel Itanium, tienen múltiples pilas . En este sentido, la pila es un elemento de la arquitectura de la CPU.
fuente
¿Qué es una pila?
Una pila es una pila de objetos, típicamente uno que está ordenado cuidadosamente.
¿Qué es un montón?
Un montón es una colección desordenada de cosas apiladas al azar.
Ambos juntos
¿Cuál es más rápido, la pila o el montón? ¿Y por qué?
Para las personas nuevas en la programación, probablemente sea una buena idea usar la pila, ya que es más fácil.
Debido a que la pila es pequeña, querrá usarla cuando sepa exactamente cuánta memoria necesitará para sus datos, o si sabe que el tamaño de sus datos es muy pequeño.
Es mejor usar el montón cuando sabe que necesitará mucha memoria para sus datos, o simplemente no está seguro de cuánta memoria necesitará (como con una matriz dinámica).
Modelo de memoria Java
La pila es el área de memoria donde se almacenan las variables locales (incluidos los parámetros del método). Cuando se trata de variables de objeto, estas son meramente referencias (punteros) a los objetos reales en el montón.
Cada vez que se crea una instancia de un objeto, se reserva una porción de memoria de almacenamiento dinámico para contener los datos (estado) de ese objeto. Como los objetos pueden contener otros objetos, algunos de estos datos pueden contener referencias a esos objetos anidados.
fuente
La pila es una parte de la memoria que puede manipularse mediante varias instrucciones clave del lenguaje ensamblador, como 'pop' (eliminar y devolver un valor de la pila) y 'push' (empujar un valor a la pila), pero también llamar ( llamar a una subrutina (esto empuja la dirección para volver a la pila) y regresar (regresar de una subrutina, esto saca la dirección de la pila y salta a ella). Es la región de memoria debajo del registro del puntero de la pila, que se puede configurar según sea necesario. La pila también se usa para pasar argumentos a las subrutinas, y también para preservar los valores en los registros antes de llamar a las subrutinas.
El montón es una porción de memoria que el sistema operativo le da a una aplicación, generalmente a través de una llamada al sistema como malloc. En los sistemas operativos modernos, esta memoria es un conjunto de páginas a las que solo el proceso de llamada tiene acceso.
El tamaño de la pila se determina en tiempo de ejecución y, en general, no crece después del lanzamiento del programa. En un programa en C, la pila debe ser lo suficientemente grande como para contener cada variable declarada dentro de cada función. El almacenamiento dinámico crecerá dinámicamente según sea necesario, pero el sistema operativo finalmente está haciendo la llamada (a menudo aumentará el almacenamiento dinámico en más del valor solicitado por malloc, por lo que al menos algunos futuros mallocs no necesitarán volver al núcleo para obtener más memoria. Este comportamiento es a menudo personalizable)
Debido a que ha asignado la pila antes de iniciar el programa, nunca necesita malloc antes de poder usar la pila, por lo que es una ligera ventaja. En la práctica, es muy difícil predecir qué será rápido y qué será lento en los sistemas operativos modernos que tienen subsistemas de memoria virtual, porque la forma en que se implementan las páginas y dónde se almacenan es un detalle de implementación.
fuente
Creo que muchas otras personas le han dado respuestas correctas sobre este asunto.
Sin embargo, un detalle que se ha pasado por alto es que el "montón" debería de hecho llamarse la "tienda gratuita". La razón de esta distinción es que la tienda gratuita original se implementó con una estructura de datos conocida como "montón binomial". Por esa razón, la asignación desde implementaciones tempranas de malloc () / free () fue la asignación desde un montón. Sin embargo, en este día moderno, la mayoría de las tiendas gratuitas se implementan con estructuras de datos muy elaboradas que no son montones binomiales.
fuente
C
lenguaje requiere el uso de una "pila" . Este es un concepto erróneo común, aunque es el paradigma dominante (de lejos) para implementarC99 6.2.4 automatic storage duration objects
(variables). De hecho, la palabra "apilar" ni siquiera aparece en elC99
lenguaje estándar: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdfPuedes hacer algunas cosas interesantes con la pila. Por ejemplo, tiene funciones como alloca (suponiendo que puede superar las abundantes advertencias sobre su uso), que es una forma de malloc que utiliza específicamente la pila, no el montón, para la memoria.
Dicho esto, los errores de memoria basados en la pila son algunos de los peores que he experimentado. Si usa memoria de almacenamiento dinámico y sobrepasa los límites de su bloque asignado, tiene una posibilidad decente de desencadenar una falla de segmento. (No 100%: su bloque puede ser incidentalmente contiguo con otro que haya asignado previamente). Pero dado que las variables creadas en la pila siempre son contiguas entre sí, escribir fuera de los límites puede cambiar el valor de otra variable. He aprendido que cada vez que siento que mi programa ha dejado de obedecer las leyes de la lógica, es probable que sea un desbordamiento del búfer.
fuente
alloca
? Por ejemplo, ¿funciona en Windows? ¿Es solo para sistemas operativos tipo Unix?Simplemente, la pila es donde se crean las variables locales. Además, cada vez que llama a una subrutina, el contador del programa (puntero a la siguiente instrucción de máquina) y cualquier registro importante, y a veces los parámetros se insertan en la pila. Luego, cualquier variable local dentro de la subrutina se inserta en la pila (y se usa desde allí). Cuando finaliza la subrutina, todo eso se vuelve a sacar de la pila. La PC y los datos de registro se vuelven a poner donde estaban cuando aparecieron, para que su programa pueda seguir su camino feliz.
El montón es el área de memoria de la que se hacen asignaciones dinámicas de memoria (llamadas "nuevas" o "asignadas" explícitas). Es una estructura de datos especial que puede realizar un seguimiento de los bloques de memoria de diferentes tamaños y su estado de asignación.
En los sistemas "clásicos", la RAM se colocaba de tal manera que el puntero de la pila comenzaba en la parte inferior de la memoria, el puntero del montón comenzaba en la parte superior y crecían uno hacia el otro. Si se superponen, se queda sin RAM. Sin embargo, eso no funciona con los sistemas operativos multiproceso modernos. Cada hilo tiene que tener su propia pila, y estos pueden crearse dinámicamente.
fuente
De WikiAnwser.
Apilar
Cuando una función o un método llama a otra función que a su vez llama a otra función, etc., la ejecución de todas esas funciones permanece suspendida hasta que la última función devuelva su valor.
Esta cadena de llamadas a funciones suspendidas es la pila, porque los elementos en la pila (llamadas a funciones) dependen unos de otros.
Es importante tener en cuenta la pila en el manejo de excepciones y las ejecuciones de subprocesos.
Montón
El montón es simplemente la memoria utilizada por los programas para almacenar variables. El elemento del montón (variables) no tiene dependencias entre sí y siempre se puede acceder al azar en cualquier momento.
fuente
Apilar
Montón
fuente
OK, simplemente y en pocas palabras, significan ordenado y no ordenado ...!
Pila : en los elementos de pila, las cosas se ponen una encima de la otra, ¡significa que será más rápido y más eficiente ser procesado! ...
Así que siempre hay un índice para señalar el elemento específico, también el procesamiento será más rápido, ¡también existe una relación entre los elementos! ...
Montón : Sin orden, el procesamiento será más lento y los valores se desordenarán sin un orden o índice específico ... hay aleatorio y no hay relación entre ellos ... por lo que el tiempo de ejecución y uso podría variar ...
También creo la imagen a continuación para mostrar cómo pueden verse:
fuente
En breve
Se utiliza una pila para la asignación de memoria estática y un montón para la asignación de memoria dinámica, ambos almacenados en la RAM de la computadora.
En detalle
La pila
La pila es una estructura de datos "LIFO" (último en entrar, primero en salir), que es administrada y optimizada por la CPU bastante de cerca. Cada vez que una función declara una nueva variable, se "inserta" en la pila. Luego, cada vez que sale una función, todas las variables empujadas a la pila por esa función se liberan (es decir, se eliminan). Una vez que se libera una variable de pila, esa región de memoria queda disponible para otras variables de pila.
La ventaja de usar la pila para almacenar variables es que la memoria se administra por usted. No tiene que asignar memoria a mano o liberarla una vez que ya no la necesita. Además, debido a que la CPU organiza la memoria de la pila de manera tan eficiente, leer y escribir en las variables de la pila es muy rápido.
Más se puede encontrar aquí .
El montón
El almacenamiento dinámico es una región de la memoria de su computadora que no se administra automáticamente para usted y que la CPU no lo hace de manera tan estricta. Es una región de memoria más flotante (y es más grande). Para asignar memoria en el montón, debe usar malloc () o calloc (), que son funciones integradas de C. Una vez que haya asignado memoria en el montón, usted es responsable de usar free () para desasignar esa memoria una vez que ya no la necesite.
Si no lo hace, su programa tendrá lo que se conoce como pérdida de memoria. Es decir, la memoria en el montón aún se reservará (y no estará disponible para otros procesos). Como veremos en la sección de depuración, hay una herramienta llamada Valgrind que puede ayudarlo a detectar pérdidas de memoria.
A diferencia de la pila, el montón no tiene restricciones de tamaño en tamaño variable (aparte de las limitaciones físicas obvias de su computadora). La memoria de almacenamiento dinámico es un poco más lenta de leer y escribir, porque uno tiene que usar punteros para acceder a la memoria en el almacenamiento dinámico. Hablaremos de punteros en breve.
A diferencia de la pila, las variables creadas en el montón son accesibles por cualquier función, en cualquier parte de su programa. Las variables de montón son esencialmente de alcance global.
Más se puede encontrar aquí .
Las variables asignadas en la pila se almacenan directamente en la memoria y el acceso a esta memoria es muy rápido, y su asignación se trata cuando se compila el programa. Cuando una función o un método llama a otra función que a su vez llama a otra función, etc., la ejecución de todas esas funciones permanece suspendida hasta que la última función devuelva su valor. La pila siempre está reservada en un orden LIFO, el bloque reservado más recientemente es siempre el siguiente bloque que se liberará. Esto hace que sea realmente simple hacer un seguimiento de la pila, liberar un bloque de la pila no es más que ajustar un puntero.
Las variables asignadas en el montón tienen su memoria asignada en tiempo de ejecución y el acceso a esta memoria es un poco más lento, pero el tamaño del montón solo está limitado por el tamaño de la memoria virtual. Los elementos del montón no tienen dependencias entre sí y siempre se puede acceder de forma aleatoria en cualquier momento. Puede asignar un bloque en cualquier momento y liberarlo en cualquier momento. Esto hace que sea mucho más complejo hacer un seguimiento de qué partes del montón están asignadas o libres en un momento dado.
Puede usar la pila si sabe exactamente cuántos datos necesita asignar antes del tiempo de compilación, y no es demasiado grande. Puede usar el montón si no sabe exactamente cuántos datos necesitará en tiempo de ejecución o si necesita asignar una gran cantidad de datos.
En una situación de subprocesos múltiples, cada subproceso tendrá su propia pila completamente independiente, pero compartirán el montón. La pila es específica de subproceso y el montón es específico de la aplicación. Es importante tener en cuenta la pila en el manejo de excepciones y las ejecuciones de subprocesos.
Cada subproceso obtiene una pila, mientras que normalmente solo hay un montón para la aplicación (aunque no es raro tener varios montones para diferentes tipos de asignación).
En tiempo de ejecución, si la aplicación necesita más almacenamiento dinámico, puede asignar memoria de la memoria libre y si la pila necesita memoria, puede asignar memoria de la memoria asignada de memoria libre para la aplicación.
Incluso, se dan más detalles aquí y aquí .
Ahora ve a las respuestas de tu pregunta .
El sistema operativo asigna la pila para cada subproceso de nivel de sistema cuando se crea el subproceso. Normalmente, el tiempo de ejecución del lenguaje llama al sistema operativo para asignar el montón para la aplicación.
Más se puede encontrar aquí .
Ya dado en la parte superior.
Más se puede encontrar aquí .
El sistema operativo establece el tamaño de la pila cuando se crea un subproceso. El tamaño del montón se establece en el inicio de la aplicación, pero puede crecer a medida que se necesita espacio (el asignador solicita más memoria del sistema operativo).
La asignación de la pila es mucho más rápida ya que todo lo que realmente hace es mover el puntero de la pila. Con los grupos de memoria, puede obtener un rendimiento comparable de la asignación de almacenamiento dinámico, pero eso viene con una ligera complejidad adicional y sus propios dolores de cabeza.
Además, stack vs. heap no es solo una consideración de rendimiento; También le informa mucho sobre la vida útil esperada de los objetos.
Los detalles se pueden encontrar desde aquí .
fuente
En la década de 1980, UNIX se propagó como conejitos con grandes compañías rodando las suyas. Exxon tenía uno, al igual que docenas de marcas perdidas en la historia. La disposición de la memoria quedaba a discreción de muchos implementadores.
Un programa C típico se presentaba plano en la memoria con la oportunidad de aumentar cambiando el valor brk (). Típicamente, el HEAP estaba justo por debajo de este valor brk y al aumentar brk aumentó la cantidad de almacenamiento dinámico disponible.
El STACK individual era típicamente un área debajo de HEAP que era un tramo de memoria que no contenía nada de valor hasta la parte superior del siguiente bloque fijo de memoria. Este siguiente bloque a menudo era CÓDIGO, que podría sobrescribirse con datos de pila en uno de los hacks famosos de su era.
Un bloque de memoria típico era BSS (un bloque de valores cero) que accidentalmente no se puso a cero en la oferta de un fabricante. Otro era DATA que contenía valores inicializados, incluidas cadenas y números. Un tercero era CODE que contenía CRT (tiempo de ejecución C), main, funciones y bibliotecas.
El advenimiento de la memoria virtual en UNIX cambia muchas de las restricciones. No hay una razón objetiva por la cual estos bloques deben ser contiguos, fijos en tamaño u ordenados de una manera particular ahora. Por supuesto, antes de que UNIX fuera Multics que no sufría estas restricciones. Aquí hay un esquema que muestra uno de los diseños de memoria de esa época.
fuente
pila , montón y datos de cada proceso en la memoria virtual:
fuente
Un par de centavos: creo que será bueno dibujar una memoria gráfica y más simple:
Flechas: muestran dónde crecen la pila y el montón, el tamaño de la pila del proceso tiene un límite, definido en el sistema operativo, los límites del tamaño de la pila de subprocesos por parámetros en la API de creación de subprocesos generalmente. El almacenamiento dinámico suele limitar el tamaño máximo de memoria virtual del proceso, por ejemplo, para 32 bits de 2 a 4 GB.
De manera tan simple: el montón de procesos es general para el proceso y todos los subprocesos en el interior, utilizando para la asignación de memoria en el caso común con algo como malloc () .
Stack es una memoria rápida para almacenar en el caso común punteros y variables de retorno de funciones, procesados como parámetros en la llamada de función, variables de función local.
fuente
Dado que algunas respuestas se volvieron quisquillosas, voy a contribuir con mi ácaro.
Sorprendentemente, nadie ha mencionado que múltiples pilas de llamadas (es decir, no relacionadas con el número de subprocesos a nivel del sistema operativo) se encuentran no solo en lenguajes exóticos (PostScript) o plataformas (Intel Itanium), sino también en fibras , hilos verdes y algunas implementaciones de corutinas .
Las fibras, los hilos verdes y las corutinas son similares en muchos aspectos, lo que genera mucha confusión. La diferencia entre las fibras y los hilos verdes es que los primeros usan la multitarea cooperativa, mientras que el segundo puede presentar uno cooperativo o preventivo (o incluso ambos). Para la distinción entre fibras y corutinas, vea aquí .
En cualquier caso, el propósito de ambas fibras, hilos verdes y corutinas es tener múltiples funciones ejecutándose simultáneamente, pero no en paralelo (ver esta pregunta SO para la distinción) dentro de un solo hilo a nivel del sistema operativo, transfiriendo el control de un lado a otro de manera organizada.
Al usar fibras, hilos verdes o corutinas, generalmente tiene una pila separada por función. (Técnicamente, no solo una pila sino un contexto completo de ejecución es por función. Lo más importante es que la CPU se registra). Para cada hilo hay tantas pilas como funciones que se ejecutan simultáneamente, y el hilo cambia entre ejecutar cada función de acuerdo con la lógica de su programa. Cuando una función llega a su fin, su pila se destruye. Por lo tanto, el número y la vida útil de las pilas son dinámicas y no están determinadas por el número de subprocesos a nivel de sistema operativo.
Tenga en cuenta que dije " generalmente tengo una pila separada por función". Hay implementaciones apiladas y apiladas de couroutines. La mayoría de las implementaciones notable stackful C ++ son Boost.Coroutine y Microsoft PPL s'
async/await
. (Sin embargo, las funciones reanudables de C ++ (también conocidas como "async
yawait
es probable que las "), que se propusieron para C ++ 17, utilicen rutinas sin pila.La propuesta de fibras para la biblioteca estándar de C ++ está próxima. Además, hay algunas bibliotecas de terceros . Los hilos verdes son extremadamente populares en lenguajes como Python y Ruby.
fuente
Tengo algo que compartir, aunque los puntos principales ya están cubiertos.
Apilar
Montón
Nota interesante:
fuente
¡Guauu! Tantas respuestas y no creo que una de ellas haya acertado ...
1) ¿Dónde y qué están (físicamente en la memoria de una computadora real)?
La pila es la memoria que comienza como la dirección de memoria más alta asignada a la imagen de su programa, y luego disminuye en valor desde allí. Está reservado para los parámetros de función llamados y para todas las variables temporales utilizadas en funciones.
Hay dos montones: público y privado.
El montón privado comienza en un límite de 16 bytes (para programas de 64 bits) o un límite de 8 bytes (para programas de 32 bits) después del último byte de código en su programa, y luego aumenta su valor desde allí. También se llama el montón predeterminado.
Si el montón privado es demasiado grande, se superpondrá al área de la pila, al igual que la pila se superpondrá al montón si se hace demasiado grande. Debido a que la pila comienza en una dirección más alta y desciende a una dirección más baja, con el pirateo adecuado puede hacer que la pila sea tan grande que sobrepase el área de montón privado y se superponga al área de código. El truco es superponer suficiente área del código que pueda enganchar en el código. Es un poco difícil de hacer y corre el riesgo de que se bloquee el programa, pero es fácil y muy efectivo.
El montón público reside en su propio espacio de memoria fuera del espacio de imagen de su programa. Es esta memoria la que será desviada al disco duro si los recursos de memoria escasean.
2) ¿En qué medida están controlados por el sistema operativo o el tiempo de ejecución del lenguaje?
El programador controla la pila, el sistema operativo administra el almacenamiento dinámico privado y nadie controla el almacenamiento dinámico público porque es un servicio del sistema operativo: usted realiza solicitudes y se otorgan o rechazan.
2b) ¿Cuál es su alcance?
Todos son globales para el programa, pero sus contenidos pueden ser privados, públicos o globales.
2c) ¿Qué determina el tamaño de cada uno de ellos?
El tamaño de la pila y el montón privado están determinados por las opciones de tiempo de ejecución de su compilador. El montón público se inicializa en tiempo de ejecución utilizando un parámetro de tamaño.
2d) ¿Qué lo hace a uno más rápido?
No están diseñados para ser rápidos, están diseñados para ser útiles. La forma en que el programador los utiliza determina si son "rápidos" o "lentos"
ÁRBITRO:
https://norasandler.com/2019/02/18/Write-a-Compiler-10.html
https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap
https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate
fuente
Muchas de las respuestas son correctas como conceptos, pero debemos tener en cuenta que el hardware necesita una pila (es decir, un microprocesador) para permitir llamar a subrutinas (LLAMAR en lenguaje ensamblador ...). (OOP chicos lo llamarán métodos )
En la pila, guarda las direcciones de retorno y la llamada → push / ret → pop se gestiona directamente en el hardware.
Puede usar la pila para pasar parámetros ... incluso si es más lenta que usar registros (diría un gurú del microprocesador o un buen libro de BIOS de la década de 1980 ...)
El uso de la pila es más rápido como:
fuente
malloc
es una llamada de kernel?Fuente: Academind
fuente
Gracias por una muy buena discusión, pero como un novato real, me pregunto dónde se guardan las instrucciones. En el PRINCIPIO, los científicos decidían entre dos arquitecturas (von NEUMANN, donde todo se considera DATOS y HARVARD, donde un área de memoria estaba reservada para instrucciones y otra para datos). Finalmente, elegimos el diseño von Neumann y ahora todo se considera "igual". Esto me hizo difícil cuando estaba aprendiendo el ensamblaje https://www.cs.virginia.edu/~evans/cs216/guides/x86.html porque hablan sobre registros y apiladores.
Todo lo anterior habla de DATOS. Supongo que, dado que una instrucción es una cosa definida con una huella de memoria específica, iría a la pila y todos los registros 'esos' discutidos en el ensamblaje están en la pila. Por supuesto, luego vino la programación orientada a objetos con instrucciones y datos que se integraron en una estructura que era dinámica, por lo que ahora las instrucciones también se mantendrían en el montón.
fuente