Pregunta ¿Qué y dónde están la pila y el montón?


Los libros de lenguajes de programación explican que los tipos de valores se crean en apilar, y los tipos de referencia se crean en montónsin explicar lo que son estas dos cosas No he leído una explicación clara de esto. Entiendo que un montón es. 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?

7116
2017-09-17 04:18


origen


Respuestas:


La pila es la memoria reservada como espacio cero 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 las variables locales y algunos datos de contabilidad. Cuando esa función retorna, el bloque no se usa y puede usarse la próxima vez que se llame a una función. La pila siempre se reserva en un orden LIFO (último en entrar, primero en salir); el bloque reservado más reciente 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.

El montón es memoria reservada para la asignación dinámica. A diferencia de la pila, no hay un patrón forzado 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 montón personalizados disponibles para ajustar el rendimiento del montón para diferentes patrones de uso.

Cada hilo obtiene una pila, mientras que normalmente solo hay un montón para la aplicación (aunque no es raro tener múltiples montones para diferentes tipos de asignación).

Para responder a sus preguntas directamente:

¿En qué medida están controlados por el sistema operativo o el tiempo de ejecución del idioma?

El sistema operativo asigna la pila para cada subproceso de nivel de sistema cuando se crea el subproceso. Normalmente, el lenguaje de ejecución llama al sistema operativo para asignar el montón para la aplicación.

¿Cuál es su alcance?

La pila está unida a un hilo, por lo que cuando el hilo sale se recupera la pila. El montón se suele asignar al inicio de la aplicación por el tiempo de ejecución y se recupera cuando finaliza la aplicación (proceso técnico).

¿Qué determina el tamaño de cada uno de ellos? 

El tamaño de la pila se establece cuando se crea un hilo. El tamaño del almacenamiento dinámico se establece al inicio de la aplicación, pero puede crecer a medida que se necesita espacio (el asignador solicita más memoria del sistema operativo).

¿Qué lo hace a uno más rápido?

La pila es más rápida porque el patrón de acceso hace que sea trivial asignar y desasignar memoria desde allí (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 al 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 varios subprocesos, es decir, cada asignación y desasignación debe ser, normalmente, sincronizada con "todos" otros accesos de montón en el programa.

Una clara demostración:
Fuente de imagen: vikashazrati.wordpress.com


5228
2017-09-17 04:52



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;

2092
2017-09-17 04:20



El punto más importante es que el montón y la pila son términos genéricos de las formas en que se puede asignar la 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 se puede quitar el superior (sin volcar todo el asunto).

    Stack like a stack of papers

    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 solo puntero al final de la pila. Para asignar y desasignar, simplemente incrementa y disminuye ese único puntero. Nota: a veces se puede implementar una pila para comenzar en la parte superior de una sección de la memoria y extenderla 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 artículos. Puede acceder y eliminar elementos en cualquier orden porque no hay un elemento claro "superior".

    Heap like a heap of licorice allsorts

    La asignación de montón requiere mantener un registro completo de qué memoria se asigna y qué no, así como también un mantenimiento general para reducir la fragmentación, encontrar segmentos de memoria contiguos lo suficientemente grandes para ajustarse al tamaño solicitado, y así sucesivamente. La memoria puede ser desasignada en cualquier momento dejando espacio libre. Algunas 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á en el alcance y deslocalándola.

Estas imágenes deberían describir bastante bien las dos formas de asignar y liberar memoria en una pila y un montón. ¡Yum!

  • ¿En qué medida están controlados por el sistema operativo o el tiempo de ejecución del idioma?

    Como se mencionó, el montón y la pila son términos generales, y se pueden implementar de muchas maneras. Los programas de computadora generalmente tienen una pila llamada pila de llamadas que almacena información relevante para la función actual, como un puntero a cualquiera de las funciones a partir de las cuales fue llamada, y cualquier variable local. Como las funciones llaman a otras funciones y luego regresan, la pila crece y se contrae para contener información de las funciones que se encuentran 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 asigna 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 realizar esta asignación. Se requiere un poco de sobrecarga en la administración de la memoria asignada dinámicamente, que generalmente es manejada por el sistema operativo.

  • ¿Cuál es su alcance?

    La pila de llamadas es un concepto de bajo nivel que no se relaciona con el 'alcance' en el sentido de programación. Si desensambla algún código, verá referencias de estilo de puntero relativo a porciones de la pila, pero en lo que se refiere a un lenguaje de nivel superior, el lenguaje impone sus propias reglas de alcance. Un aspecto importante de una pila, sin embargo, es que una vez que una función retorna, cualquier elemento local de esa función se libera inmediatamente de la pila. Eso funciona de la manera en que esperaría que funcione dado el funcionamiento de sus 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 SO utilizan el direccionamiento virtual, que el procesador traduce a direcciones físicas y hay fallas de página, etc. Se mantienen al tanto de qué páginas pertenecen a qué aplicaciones. Sin embargo, nunca tendrá que preocuparse por esto, simplemente use el método que use su lenguaje de programación para asignar y liberar memoria y verifique 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, compilador, sistema operativo y arquitectura. Una pila generalmente se asigna previamente, porque por definición debe ser memoria contigua (más sobre esto en el último párrafo). El compilador de lenguaje 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 utilice por completo, excepto en casos de recursión interminable no deseada (por lo tanto, "desbordamiento de la pila") u otras decisiones inusuales de programación.

    Un montón es un término general para todo lo que se puede asignar dinámicamente. Según la forma en que lo mires, cambia constantemente de tamaño. En los procesadores modernos y sistemas operativos, la forma exacta en que funciona está muy abstraída de todos modos, por lo que normalmente no tendrá que preocuparse mucho sobre cómo funciona en el fondo, excepto que (en los idiomas donde lo permita) no debe usar memoria que usted no ha asignado todavía o la 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 suelen almacenar este puntero de forma especial y rápida registro para este propósito. Además, las operaciones posteriores en una pila normalmente se concentran en áreas de memoria muy cercanas, lo que a un nivel muy bajo es bueno para la optimización de los cachés en memoria del procesador.


1259
2018-03-19 14:38



(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. 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 meta informacion 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, puedes 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 en el que hay muchos pequeños bloques libres 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. Se llama fragmentación del 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.

The heap

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 emprendedor valores en la pila y haciendo estallar ellos de regreso de la pila. Cada empujar almacena el valor en la ubicación actual del puntero de pila y disminuye el puntero de pila. UN popular 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 agregando un valor para la pila disminuye el puntero de la pila y eliminar un valor aumenta eso. Recuerde que la pila crece hasta 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 impulsan la corriente puntero de instrucción, es decir, la dirección del código que se ejecuta en la pila. La CPU salta a la función configurando el puntero de instrucción a 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 puede apilar para llamadas anidadas y se desenrollará correctamente cuando vuelvan las funciones.
  • Como la pila es un bloque limitado de memoria, puede causar un desbordamiento de pila llamando 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.

The stack

¿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.


663
2017-07-31 15:54



En el siguiente código C #

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

Así es como se maneja la memoria

Picture of variables on the stack

Local Variables que solo debe durar siempre que la invocación de la función entre en la pila. El montón se usa para variables cuya duración no conocemos por adelantado, pero esperamos que duren un tiempo. En la mayoría de los lenguajes es crítico 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 en el montón porque no sabemos en el momento de la creación cuánto durarán. En muchos idiomas, el montón es basura recolectada para encontrar objetos (como el objeto cls1) que ya no tienen ninguna referencia.

En Java, la mayoría de los objetos van directamente al montón. En idiomas 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 pila y memoria de memoria «timmurphy.org

y aquí:

Creando 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 desembalaje - CodeProject

pero ten en cuenta que puede contener algunas inexactitudes.


350
2017-11-09 12:28



La pila Cuando llama 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 colocar 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 los juegos en los que se carga un nivel en una operación enorme y se puede descartar en una gran operación.

Ubicación física en la memoria Esto es menos relevante de lo que crees debido a una tecnología llamada Memoria virtual lo que hace que su programa piense que tiene 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 no son predecibles (es decir, específicas de la implementación) y francamente no son importantes.


190
2017-09-17 04:27



Para aclarar, esta respuesta tiene información incorrecta (Thomas corrigió su respuesta después de los comentarios, genial :)). Otras respuestas simplemente evitan explicar qué significa la asignación estática. Entonces explicaré las tres formas principales de asignación y cómo se relacionan generalmente con el montón, la pila y el segmento de datos a continuación. También mostraré algunos ejemplos tanto en C / C ++ como en Python para ayudar a las personas a entender.

Las variables "estáticas" (AKA estáticamente asignadas) no están asignadas en la pila. No asuma eso, muchas personas lo hacen solo porque "estático" suena muy parecido a "pila". En realidad no existen ni en la pila ni en el montón. Son parte de lo que se llama el segmento de datos.

Sin embargo, generalmente es mejor considerarlo "alcance"y"toda la vida"en lugar de" apilar "y" apilar ".

El alcance se refiere a qué partes del código pueden acceder a una variable. Generalmente pensamos en alcance local (solo se puede acceder por la función actual) versus alcance global (se puede acceder a cualquier parte) aunque el alcance puede ser mucho más complejo.

Lifetime se refiere a cuando una variable es asignada y desasignada durante la ejecución del programa. Por lo general, pensamos en 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 función) versus 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 usar stacks, montones, etc., un compilador a veces puede romper estas convenciones si lo desea siempre que 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 existan en la pila. Como se ha señalado en algunos comentarios, es libre de implementar un compilador que ni siquiera usa una pila o un montón, sino otros mecanismos de almacenamiento (raramente, ya que las pilas y montones son excelentes para esto).

Proporcionaré un código C anotado simple para ilustrar todo esto. La mejor forma de aprender es ejecutar un programa bajo un depurador y observar el comportamiento. Si prefiere leer Python, salte hasta el final de la respuesta :)

// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be "leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

Un ejemplo particularmente conmovedor de por qué es importante distinguir entre duración y alcance es que una variable puede tener alcance local pero duración estática, por ejemplo, "someLocalStaticVariable" en el ejemplo de código anterior. Tales variables pueden hacer que nuestros hábitos de nombres comunes pero informales sean muy confusos. Por ejemplo, cuando decimos "local"generalmente queremos decir"variable asignada automáticamente de ámbito local"y cuando decimos global, generalmente queremos decir"variable asignada estáticamente de ámbito global". Desafortunadamente cuando se trata de cosas como"archivo de ámbito variables asignadas estáticamente"mucha gente solo dice ..."¿eh?".

Algunas de las opciones de sintaxis en C / C ++ agravan 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.

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

Tenga en cuenta que colocar la palabra clave "static" en la declaración anterior evita que var2 tenga un alcance global. Sin embargo, la var1 global tiene una asignación estática. Esto no es intuitivo! Por esta razón, trato de nunca usar la palabra "estático" cuando describo el alcance, y en cambio decir algo como "archivo" o alcance de "archivo limitado". Sin embargo, muchas personas usan la frase "estático" 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, "estático" siempre significa que la variable se asigna al inicio del programa y se desasigna cuando sale el programa.

Algunas personas piensan que estos conceptos son específicos de C / C ++. Ellos 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 que no incluiré aquí).

from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __name__ == "__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones

168
2017-09-17 04:48



Otros han respondido bastante bien a los golpes amplios, así que incluiré algunos detalles.

  1. 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 una cadena 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 como resultado diferentes DLL que se asignan desde diferentes montones, razón por la cual generalmente es una mala idea liberar memoria asignada por una biblioteca diferente.

  2. En C puede obtener el beneficio de la asignación de longitud variable mediante el uso de alloca, que asigna en la pila, a diferencia 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 rayado.

  3. Hacer un gran búfer temporal en Windows que no usas mucho no es gratis. Esto se debe a que el compilador generará un bucle de sondeo de pila que se llama cada vez que se ingresa su función para asegurarse de que exista (porque Windows usa una sola página de protección al final de la pila para detectar cuándo necesita crecer la pila. Si accede a la memoria más de una página del final de la pila se bloqueará). Ejemplo:

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

155
2017-09-17 07:16



Otros han respondido directamente a su pregunta, pero al tratar de comprender la pila y el montón, creo que es útil considerar el diseño de la memoria de un proceso UNIX tradicional (sin hilos y mmap()asignadores basados ​​en la base). los Glosario de gestión de memoria la página web tiene un diagrama de este diseño de memoria.

La pila y el montón se encuentran tradicionalmente en extremos opuestos del espacio de direcciones virtuales del proceso. La pila crece automáticamente cuando se accede, hasta un tamaño establecido por el kernel (que se puede ajustar con setrlimit(RLIMIT_STACK, ...)) El montón crece cuando el asignador de memoria invoca el brk() o sbrk() llamada al sistema, asignando más páginas de memoria física al espacio de direcciones virtuales del proceso.

En los 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 se fijan en tamaño. Sin embargo, en otros sistemas integrados (como los basados ​​en microcontroladores Microchip PIC), la pila de programas es un bloque de memoria separado que no puede ser direccionado por las instrucciones de movimiento de datos, y solo puede modificarse o leerse indirectamente mediante instrucciones de flujo de programa (llamada, regreso, 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.


126
2017-09-17 04:57