[Memory-management] ¿Qué y dónde están la pila y el montón?


Answers

Apilar:

  • Almacenado en la memoria RAM de la computadora al igual que el montón.
  • Las variables creadas en la pila saldrán del alcance y se desasignarán automáticamente.
  • Mucho más rápido de asignar en comparación con las variables en el montón.
  • Implementado con una estructura de datos de pila real.
  • Almacena datos locales, direcciones de retorno, usadas para el paso de parámetros.
  • Puede tener un desbordamiento de pila cuando se utiliza una gran parte de la pila (principalmente de recursividad infinita o demasiado profunda, asignaciones muy grandes).
  • Los datos creados en la pila se pueden usar sin punteros.
  • Utilizaría la pila si sabe exactamente la cantidad de datos que necesita asignar antes del tiempo de compilación y no es demasiado grande.
  • Usualmente tiene un tamaño máximo ya determinado cuando se inicia su programa.

Montón:

  • Almacenado en la memoria RAM de la computadora al igual que la pila.
  • En C ++, las variables en el montón deben destruirse manualmente y nunca quedar fuera del alcance. Los datos se liberan con delete , delete[] o free .
  • Más lento para asignar en comparación con las variables en la pila.
  • Usado a pedido para asignar un bloque de datos para uso del programa.
  • Puede tener fragmentación cuando hay muchas asignaciones y desasignaciones.
  • En C ++ o C, los datos creados en el montón se señalarán mediante punteros y se asignarán con new o malloc respectivamente.
  • Puede tener fallas de asignación si se solicita que se asigne un búfer demasiado grande.
  • Utilizaría 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.
  • Responsable de fugas de memoria.

Ejemplo:

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;
Question

Los libros de programación de idiomas explican que los tipos de valores 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é son (físicamente en la memoria de una computadora real)?

  • ¿En qué medida están controlados por el sistema operativo o el tiempo de ejecución del idioma?
  • ¿Cuál es su alcance?
  • ¿Qué determina el tamaño de cada uno de ellos?
  • ¿Qué lo hace a uno más rápido?



Simply, the stack is where local variables get created. Also, every time you call a subroutine the program counter (pointer to the next machine instruction) and any important registers, and sometimes the parameters get pushed on the stack. Then any local variables inside the subroutine are pushed onto the stack (and used from there). When the subroutine finishes, that stuff all gets popped back off the stack. The PC and register data gets and put back where it was as it is popped, so your program can go on its merry way.

The heap is the area of memory dynamic memory allocations are made out of (explicit "new" or "allocate" calls). It is a special data structure that can keep track of blocks of memory of varying sizes and their allocation status.

In "classic" systems RAM was laid out such that the stack pointer started out at the bottom of memory, the heap pointer started out at the top, and they grew towards each other. If they overlap, you are out of RAM. That doesn't work with modern multi-threaded OSes though. Every thread has to have its own stack, and those can get created dynamicly.




La pila Cuando llamas a una función, los argumentos a esa función más algunos otros gastos generales se ponen en la pila. También se almacena cierta información (por ejemplo, dónde ir para regresar). Cuando declaras una variable dentro de tu función, esa variable también se asigna en la pila.

La desasignación de la pila es bastante simple porque siempre se desasigna en el orden inverso al que se asigna. Las cosas apiladas se agregan a medida que ingresas funciones, los datos correspondientes se eliminan al salir de ellos. 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 crea una solución recursiva).

El montón El montón es un nombre genérico para el lugar donde colocas los datos que creas sobre la marcha. Si no sabe cuántas naves espaciales creará su programa, es probable que utilice 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 intercaladas con fragmentos que son: la memoria se fragmenta. Encontrar memoria libre del tamaño que necesita es un problema difícil. Esta es la razón por la que se debe evitar el montón (aunque todavía se usa con frecuencia).

Implementación La implementación tanto de la pila como del montón suele ser hasta el tiempo de ejecución / sistema operativo. 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 el uso de la memoria es bastante diferente de la norma, es decir, para juegos donde se carga un nivel en una operación enorme y se puede tirar todo el lote en otra operación enorme.

Ubicación física en la memoria Esto es menos relevante de lo que piensas debido a una tecnología llamada Memoria Virtual que hace que tu programa piense que tienes acceso a cierta dirección donde los datos físicos están en otro lugar (¡incluso en el disco duro!). Las direcciones que obtienes para la pila están en orden creciente a medida que tu árbol de llamadas se hace más profundo. Las direcciones para el montón son impredecibles (es decir, implícitas específicas) y francamente no son importantes.




Apilar

  • Very fast access
  • Don't have to explicitly de-allocate variables
  • Space is managed efficiently by CPU, memory will not become fragmented
  • Local variables only
  • Limit on stack size (OS-dependent)
  • Variables cannot be resized

Heap

  • Variables can be accessed globally
  • No limit on memory size
  • (Relatively) slower access
  • No guaranteed efficient use of space, memory may become fragmented over time as blocks of memory are allocated, then freed
  • You must manage memory (you're in charge of allocating and freeing variables)
  • Variables can be resized using realloc()



A couple of cents: I think, it will be good to draw memory graphical and more simple:


Arrows - show where grow stack and heap, process stack size have limit, defined in OS, thread stack size limits by parameters in thread create API usually. Heap usually limiting by process maximum virtual memory size, for 32 bit 2-4 GB for example.

So simple way: process heap is general for process and all threads inside, using for memory allocation in common case with something like malloc() .

Stack is quick memory for store in common case function return pointers and variables, processed as parameters in function call, local function variables.




What is a stack?

A stack is a pile of objects, typically one that is neatly arranged.

Stacks in computing architectures are regions of memory where data is added or removed in a last-in-first-out manner.
In a multi-threaded application, each thread will have its own stack.

What is a heap?

A heap is an untidy collection of things piled up haphazardly.

In computing architectures the heap is an area of dynamically-allocated memory that is managed automatically by the operating system or the memory manager library.
Memory on the heap is allocated, deallocated, and resized regularly during program execution, and this can lead to a problem called fragmentation.
Fragmentation occurs when memory objects are allocated with small spaces in between that are too small to hold additional memory objects.
The net result is a percentage of the heap space that is not usable for further memory allocations.

Both together

In a multi-threaded application, each thread will have its own stack. But, all the different threads will share the heap.
Because the different threads share the heap in a multi-threaded application, this also means that there has to be some coordination between the threads so that they don't try to access and manipulate the same piece(s) of memory in the heap at the same time.

Which is faster – the stack or the heap? And why?

The stack is much faster than the heap.
This is because of the way that memory is allocated on the stack.
Allocating memory on the stack is as simple as moving the stack pointer up.

For people new to programming, it's probably a good idea to use the stack since it's easier.
Because the stack is small, you would want to use it when you know exactly how much memory you will need for your data, or if you know the size of your data is very small.
It's better to use the heap when you know that you will need a lot of memory for your data, or you just are not sure how much memory you will need (like with a dynamic array).

Java Memory Model

The stack is the area of memory where local variables (including method parameters) are stored. When it comes to object variables, these are merely references (pointers) to the actual objects on the heap.
Every time an object is instantiated, a chunk of heap memory is set aside to hold the data (state) of that object. Since objects can contain other objects, some of this data can in fact hold references to those nested objects.




(He movido esta respuesta de otra pregunta que fue más o menos una trampa 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.

  • Tanto la pila como el montón son áreas de memoria asignadas desde el sistema operativo subyacente (a menudo la memoria virtual asignada a la memoria física a pedido).
  • En un entorno de subprocesos múltiples, cada subproceso tendrá su propia pila completamente independiente, pero compartirán el montón. El acceso concurrente debe controlarse en el montón y no es posible en la pila.

El montón

  • El montón contiene una lista vinculada de bloques usados ​​y libres. Las nuevas asignaciones en el montón (por new o malloc ) 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 en frente de cada bloque.
  • A medida que crece el montón, los bloques nuevos a menudo se asignan desde direcciones inferiores hacia direcciones superiores. Por lo tanto, puede pensar en el montón como un montón de bloques de memoria que crece en tamaño a medida que se asigna la memoria. Si el montón es demasiado pequeño para una asignación, el tamaño a menudo se puede aumentar al adquirir más memoria del sistema operativo subyacente.
  • Asignar y desasignar muchos bloques pequeños puede dejar el montón en un estado donde hay muchos bloques libres pequeños intercalados entre los bloques usados. Una solicitud para asignar un bloque grande puede fallar porque ninguno de los bloques libres es lo suficientemente grande como para satisfacer la solicitud de asignación, aunque el tamaño combinado de los bloques libres puede ser lo suficientemente grande. Esto se llama fragmentación de montón .
  • Cuando un bloque usado que está adyacente a un bloque libre es desasignado, el nuevo bloque libre puede fusionarse con el bloque libre adyacente para crear un bloque libre más grande que reduce efectivamente la fragmentación del montón.

La pila

  • La pila a menudo funciona en tándem con un registro especial en la CPU llamado puntero de la pila . Inicialmente, el puntero de pila apunta a la parte superior de la pila (la dirección más alta en la pila).
  • La CPU tiene instrucciones especiales para empujar los valores a la pila y sacarlos de la pila. Cada inserción almacena el valor en la ubicación actual del puntero de pila y disminuye el puntero de pila. Un pop recupera el valor apuntado por el puntero de la pila y luego aumenta el puntero de la pila (no se confunda por el hecho de que al agregar un valor a la pila disminuye el puntero de la pila y la eliminación de un valor aumenta . Recuerde que la pila crece El fondo). Los valores almacenados y recuperados son los valores de los registros de la CPU.
  • Cuando se llama a una función, la CPU usa instrucciones especiales que presionan el puntero de instrucción actual, es decir, la dirección del código que se ejecuta en la pila. Luego, la CPU salta a la función configurando el puntero de instrucción en la dirección de la función llamada. Más tarde, cuando la función retorna, el puntero de instrucción anterior se saca de la pila y la ejecución se reanuda en el código justo después de la llamada a la función.
  • Cuando se ingresa una función, el puntero de la pila se reduce para asignar más espacio en la pila para las variables locales (automáticas). Si la función tiene una variable local de 32 bits, se reservan cuatro bytes en la pila. Cuando la función retorna, el puntero de la pila se mueve hacia atrás para liberar el área asignada.
  • Si una función tiene parámetros, estos se envían a la pila antes de la llamada a la función. El código en la función es capaz de navegar por la pila desde el puntero de la pila actual para localizar estos valores.
  • Las llamadas a función de anidación funcionan como un amuleto. Cada nueva llamada asignará los parámetros de la función, la dirección de retorno y el espacio para las variables locales y estos registros de activación se pueden apilar para las llamadas anidadas y se desenrollarán de la manera correcta cuando regresen las funciones.
  • Como la pila es un bloque limitado de memoria, puede causar un desbordamiento de la pila llamando a demasiadas funciones anidadas y / o asignando demasiado espacio para las variables locales. A menudo, el área de memoria utilizada para la pila se configura de tal manera que escribir debajo de la parte inferior (la dirección más baja) de la pila activará una trampa o excepción en la CPU. Esta condición excepcional puede ser capturada por el tiempo de ejecución y convertida en algún tipo de excepción de desbordamiento de pila.

¿Se puede asignar una función en el montón en lugar de una 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 ++ usa new , pero muchos otros idiomas tienen recolección de basura.

Sin embargo, la pila es una característica más de bajo nivel 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, crecer la pila es a menudo imposible ya que el desbordamiento de la pila solo se descubre cuando ya es demasiado tarde; y cerrar el hilo de la ejecución es la única opción viable.




Others have answered the broad strokes pretty well, so I'll throw in a few details.

  1. Stack and heap need not be singular. A common situation in which you have more than one stack is if you have more than one thread in a process. In this case each thread has its own stack. You can also have more than one heap, for example some DLL configurations can result in different DLLs allocating from different heaps, which is why it's generally a bad idea to release memory allocated by a different library.

  2. In C you can get the benefit of variable length allocation through the use of alloca , which allocates on the stack, as opposed to alloc, which allocates on the heap. This memory won't survive your return statement, but it's useful for a scratch buffer.

  3. Making a huge temporary buffer on Windows that you don't use much of is not free. This is because the compiler will generate a stack probe loop that is called every time your function is entered to make sure the stack exists (because Windows uses a single guard page at the end of your stack to detect when it needs to grow the stack. If you access memory more than one page off the end of the stack you will crash). Ejemplo:

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}



I think many other people have given you mostly correct answers on this matter.

One detail that has been missed, however, is that the "heap" should in fact probably be called the "free store". The reason for this distinction is that the original free store was implemented with a data structure known as a "binomial heap." For that reason, allocating from early implementations of malloc()/free() was allocation from a heap. However, in this modern day, most free stores are implemented with very elaborate data structures that are not binomial heaps.




  • Introducción

Physical memory is the range of the physical addresses of the memory cells in which an application or system stores its data, code, and so on during execution. Memory management denotes the managing of these physical addresses by swapping the data from physical memory to a storage device and then back to physical memory when needed. The OS implements the memory management services using virtual memory. As a C# application developer you do not need to write any memory management services. The CLR uses the underlying OS memory management services to provide the memory model for C# or any other high-level language targeting the CLR.

Figure 4-1 shows physical memory that has been abstracted and managed by the OS, using the virtual memory concept. Virtual memory is the abstract view of the physical memory, managed by the OS. Virtual memory is simply a series of virtual addresses, and these virtual addresses are translated by the CPU into the physical address when needed.

Figure 4-1. CLR memory abstraction

The CLR provides the memory management abstract layer for the virtual execution environment, using the operating memory services. The abstracted concepts the CLR uses are AppDomain, thread, stack, heapmemorymapped file, and so on. The concept of the application domain (AppDomain) gives your application an isolated execution environment.

  • Memory Interaction between the CLR and OS

By looking at the stack trace while debugging the following C# application, using WinDbg, you will see how the CLR uses the underlying OS memory management services (eg, the HeapFree method from KERNEL32.dll, the RtlpFreeHeap method from ntdll.dll) to implement its own memory model:

using System;
namespace CH_04
{
    class Program
    {
        static void Main(string[] args)
        {
            Book book = new Book();
            Console.ReadLine();
        }
    }

    public class Book
    {
        public void Print() { Console.WriteLine(ToString()); }
    }
}

The compiled assembly of the program is loaded into WinDbg to start debugging. You use the following commands to initialize the debugging session:

0:000> sxe ld clrjit

0:000> g

0:000> .loadby sos clr

0:000> .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll

Then, you set a breakpoint at the Main method of the Program class, using the !bpmd command:

0:000>!bpmd CH_04.exe CH_04.Program.Main

To continue the execution and break at the breakpoint, execute the g command:

0:000> g

When the execution breaks at the breakpoint, you use the !eestack command to view the stack trace details of all threads running for the current process. The following output shows the stack trace for all the threads running for the application CH_04.exe:

0:000> !eestack

Thread 0

Current frame: (MethodDesc 00233800 +0 CH_04.Program.Main(System.String[]))

ChildEBP RetAddr Caller, Callee

0022ed24 5faf21db clr!CallDescrWorker+0x33

/ trace removed /

0022f218 77712d68 ntdll!RtlFreeHeap+0x142, calling ntdll!RtlpFreeHeap

0022f238 771df1ac KERNEL32!HeapFree+0x14, calling ntdll!RtlFreeHeap

0022f24c 5fb4c036 clr!EEHeapFree+0x36, calling KERNEL32!HeapFree

0022f260 5fb4c09d clr!EEHeapFreeInProcessHeap+0x24, calling clr!EEHeapFree

0022f274 5fb4c06d clr!operator delete[]+0x30, calling clr!EEHeapFreeInProcessHeap / trace removed /

0022f4d0 7771316f ntdll!RtlpFreeHeap+0xb7a, calling ntdll!_SEH_epilog4

0022f4d4 77712d68 ntdll!RtlFreeHeap+0x142, calling ntdll!RtlpFreeHeap

0022f4f4 771df1ac KERNEL32!HeapFree+0x14, calling ntdll!RtlFreeHeap

/ trace removed /

This stack trace indicates that the CLR uses OS memory management services to implement its own memory model. Any memory operation in.NET goes via the CLR memory layer to the OS memory management layer.

Figure 4-2 illustrates a typical C# application memory model used by the CLR at runtime.

Figure 4-2 . A typical C# application memory model

The CLR memory model is tightly coupled with the OS memory management services. To understand the CLR memory model, it is important to understand the underlying OS memory model. It is also crucial to know how the physical memory address space is abstracted into the virtual memory address space, the ways the virtual address space is being used by the user application and system application, how virtual-to-physical address mapping works, how memory-mapped file works, and so on. This background knowledge will improve your grasp of CLR memory model concepts, including AppDomain, stack, and heap.

For more information, refer to this book:

C# Deconstructed: Discover how C# works on the .NET Framework

This book + ClrViaC# + Windows Internals are excellent resources to known .net framework in depth and relation with OS.




I have something to share with you, although major points are already penned.

Apilar

  • Very fast access.
  • Stored in RAM.
  • Function calls are loaded here along with the local variables and function parameters passed.
  • Space is freed automatically when program goes out of a scope.
  • Stored in sequential memory.

Heap

  • Slow access comparatively to Stack.
  • Stored in RAM.
  • Dynamically created variables are stored here, which later requires freeing the allocated memory after use.
  • Stored wherever memory allocation is done, accessed by pointer always.

Interesting note:

  • Should the function calls had been stored in heap, it would had resulted in 2 messy points:
    1. Due to sequential storage in stack, execution is faster. Storage in heap would have resulted in huge time consumption thus resulting whole program to execute slower.
    2. If functions were stored in heap (messy storage pointed by pointer), there would have been no way to return to the caller address back (which stack gives due to sequential storage in memory).

Feedbacks are wellcomed.




OK, simply and in short words, they mean ordered and not ordered ...!

Stack : In stack items, things get on the top of each-other, means gonna be faster and more efficient to be processed!...

So there is always an index to point the specific item, also processing gonna be faster, there is relationship between the items as well!...

Heap : No order, processing gonna be slower and values are messed up together with no specific order or index... there are random and there is no relationship between them... so execution and usage time could be vary...

I also create the image below to show how they may look like: