Что и где находится стек и куча?

Книги по языку программирования объясняют, что типы значений создаются в стеке, а ссылочные типы создаются в куче, без объяснения того, что представляют собой эти две вещи. Я не прочитал четкое объяснение этого. Я понимаю, что такое стек. Но,

  • где и что они (физически в памяти реального компьютера)?
  • В какой степени они контролируются ОС или языковой средой выполнения?
  • Какова их сфера применения?
  • От чего зависит размер каждого из них?
  • Что делает быстрее?

34 ответа

Решение

Стек - это память, выделенная как пустое место для потока выполнения. Когда вызывается функция, блок резервируется в верхней части стека для локальных переменных и некоторых бухгалтерских данных. Когда эта функция возвращается, блок становится неиспользуемым и может использоваться при следующем вызове функции. Стек всегда зарезервирован в порядке LIFO (последний в порядке поступления); последний зарезервированный блок всегда является следующим блоком, который должен быть освобожден. Это действительно упрощает отслеживание стека; освобождение блока из стека - это не более, чем настройка одного указателя.

Куча памяти выделяется для динамического выделения. В отличие от стека, нет принудительного шаблона для распределения и освобождения блоков из кучи; Вы можете выделить блок в любое время и освободить его в любое время. Это значительно усложняет отслеживание того, какие части кучи выделены или свободны в любой момент времени; Есть много пользовательских распределителей кучи, доступных для настройки производительности кучи для различных моделей использования.

Каждый поток получает стек, в то время как для приложения обычно есть только одна куча (хотя весьма часто иметь несколько куч для разных типов размещения).

Чтобы ответить на ваши вопросы напрямую:

В какой степени они контролируются ОС или языковой средой выполнения?

ОС выделяет стек для каждого потока системного уровня при создании потока. Обычно ОС вызывается языковой средой выполнения для выделения кучи для приложения.

Какова их сфера применения?

Стек присоединен к потоку, поэтому, когда поток выходит из стека, он освобождается. Куча обычно выделяется при запуске приложения средой выполнения и восстанавливается при выходе из приложения (технически процесса).

От чего зависит размер каждого из них?

Размер стека устанавливается при создании потока. Размер кучи устанавливается при запуске приложения, но может увеличиваться по мере необходимости (распределитель запрашивает больше памяти у операционной системы).

Что делает быстрее?

Стек работает быстрее, потому что шаблон доступа упрощает выделение и освобождение памяти из него (указатель / целое число просто увеличивается или уменьшается), в то время как куча имеет гораздо более сложную бухгалтерию, связанную с выделением или освобождением. Кроме того, каждый байт в стеке имеет тенденцию использоваться очень часто, что означает, что он имеет тенденцию отображаться в кэш процессора, что делает его очень быстрым. Другим ударом производительности для кучи является то, что куча, являющаяся главным образом глобальным ресурсом, как правило, должна быть многопоточной безопасной, то есть каждое распределение и освобождение должно быть - как правило - синхронизировано со "всеми" другими обращениями к куче в программе.

Четкая демонстрация:
Источник изображения: vikashazrati.wordpress.com

стек:

  • Хранится в оперативной памяти компьютера, как в куче.
  • Переменные, созданные в стеке, выйдут из области видимости и будут автоматически освобождены.
  • Гораздо быстрее выделить по сравнению с переменными в куче.
  • Реализовано с фактической структурой данных стека.
  • Хранит локальные данные, обратные адреса, используемые для передачи параметров.
  • Может иметь переполнение стека, когда используется слишком большая часть стека (в основном из-за бесконечной или слишком глубокой рекурсии, очень больших выделений).
  • Данные, созданные в стеке, могут использоваться без указателей.
  • Вы бы использовали стек, если точно знаете, сколько данных нужно выделить перед компиляцией, и он не слишком велик.
  • Обычно максимальный размер уже определен при запуске вашей программы.

Heap:

  • Хранится в оперативной памяти компьютера так же, как в стеке.
  • В C++ переменные в куче должны быть уничтожены вручную и никогда не выпадают из области видимости. Данные освобождаются с delete, delete[], или же free,
  • Медленнее выделять по сравнению с переменными в стеке.
  • Используется по требованию для выделения блока данных для использования программой.
  • Может иметь фрагментацию, когда есть много выделений и освобождений.
  • В C++ или C данные, созданные в куче, будут указываться указателями и размещаться с new или же malloc соответственно.
  • Может иметь ошибки выделения, если запрошен слишком большой буфер.
  • Вы бы использовали кучу, если не знаете точно, сколько данных вам понадобится во время выполнения или если вам нужно выделить много данных.
  • Ответственный за утечки памяти.

Пример:

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;

Наиболее важным моментом является то, что куча и стек являются общими терминами для способов выделения памяти. Они могут быть реализованы многими различными способами, и эти термины применяются к основным понятиям.

  • В пачке предметов предметы располагаются друг над другом в том порядке, в котором они были там размещены, и вы можете удалить только верхний (не переворачивая все это).

    Стопка, как стопка бумаг

    Простота стека заключается в том, что вам не нужно поддерживать таблицу, содержащую запись каждого раздела выделенной памяти; единственная информация о состоянии, которая вам нужна, - это единственный указатель на конец стека. Чтобы выделить и удалить, вы просто увеличиваете и уменьшаете этот единственный указатель. Примечание: иногда может быть реализован стек, который начинается в верхней части раздела памяти и расширяется вниз, а не растёт вверх.

  • В куче нет определенного порядка расположения предметов. Вы можете входить и удалять предметы в любом порядке, потому что нет четкого "верхнего" предмета.

    Куча как солодка ассорти

    Выделение кучи требует ведения полной записи о том, какая память выделена, а что нет, а также некоторых служебных расходов для уменьшения фрагментации, поиска смежных сегментов памяти, достаточно больших, чтобы соответствовать требуемому размеру, и так далее. Память может быть освобождена в любое время, оставляя свободное место. Иногда распределитель памяти выполняет задачи обслуживания, такие как дефрагментация памяти, перемещая выделенную память или собирая мусор - определяя во время выполнения, когда память больше не находится в области действия, и освобождая ее.

Эти образы должны довольно хорошо описать два способа выделения и освобождения памяти в стеке и куче. Yum!

  • В какой степени они контролируются ОС или языковой средой выполнения?

    Как уже упоминалось, куча и стек являются общими терминами и могут быть реализованы многими способами. Компьютерные программы обычно имеют стек, называемый стеком вызовов, в котором хранится информация, относящаяся к текущей функции, например указатель на функцию, из которой она была вызвана, и любые локальные переменные. Поскольку функции вызывают другие функции и затем возвращаются, стек увеличивается и сжимается, чтобы хранить информацию от функций далее в стеке вызовов. Программа на самом деле не имеет контроля над ней во время выполнения; это определяется языком программирования, ОС и даже архитектурой системы.

    Куча - это общий термин, используемый для любой памяти, которая выделяется динамически и случайным образом; т.е. не в порядке. Память обычно выделяется ОС, при этом приложение вызывает функции API для этого выделения. При управлении динамически распределяемой памятью требуются значительные накладные расходы, которые обычно обрабатываются ОС.

  • Какова их сфера применения?

    Стек вызовов является настолько низкоуровневым понятием, что не имеет отношения к "объему" в смысле программирования. Если вы разберете некоторый код, вы увидите относительные ссылки на стиль указателя на части стека, но в отношении языка более высокого уровня язык накладывает свои собственные правила области видимости. Однако одним важным аспектом стека является то, что, как только функция возвращается, все, что является локальным для этой функции, немедленно освобождается из стека. Это работает так, как вы ожидаете, учитывая, как работают ваши языки программирования. В куче это тоже сложно определить. Область действия - это то, что предоставляется операционной системой, но ваш язык программирования, вероятно, добавляет свои правила о том, что такое "область действия" в вашем приложении. Архитектура процессора и ОС используют виртуальную адресацию, которую процессор преобразует в физические адреса, имеются сбои страниц и т. Д. Они отслеживают, какие страницы принадлежат каким приложениям. Однако вам никогда не нужно беспокоиться об этом, потому что вы просто используете любой метод, используемый вашим языком программирования для выделения и освобождения памяти, и проверяете на наличие ошибок (если по какой-либо причине распределение / освобождение не удается).

  • От чего зависит размер каждого из них?

    Опять же, это зависит от языка, компилятора, операционной системы и архитектуры. Стек обычно выделяется заранее, потому что по определению он должен быть смежной памятью (подробнее об этом в последнем абзаце). Компилятор языка или ОС определяют его размер. Вы не храните огромные порции данных в стеке, поэтому они будут достаточно большими, чтобы никогда не использовать их полностью, за исключением случаев нежелательной бесконечной рекурсии (отсюда "переполнение стека") или других необычных программных решений.

    Куча - это общий термин для всего, что может быть динамически выделено. В зависимости от того, как вы на это смотрите, размер постоянно меняется. В современных процессорах и операционных системах точный способ его работы в любом случае очень абстрагирован, поэтому обычно вам не нужно сильно беспокоиться о том, как он работает в глубине, за исключением того, что (в языках, где это позволяет) вы не должны использовать память, которая Вы еще не распределили или память, которую вы освободили.

  • Что делает быстрее?

    Стек быстрее, потому что вся свободная память всегда смежна. Не нужно поддерживать список всех сегментов свободной памяти, только один указатель на текущую вершину стека. Для этой цели компиляторы обычно хранят этот указатель в специальном быстром регистре. Более того, последующие операции со стеком обычно сосредоточены в очень близких областях памяти, что на очень низком уровне хорошо для оптимизации кэш-памятью процессора.

(I have moved this answer from another question that was more or less a dupe of this one.)

The answer to your question is implementation specific and may vary across compilers and processor architectures. However, here is a simplified explanation.

  • Both the stack and the heap are memory areas allocated from the underlying operating system (often virtual memory that is mapped to physical memory on demand).
  • In a multi-threaded environment each thread will have its own completely independent stack but they will share the heap. Concurrent access has to be controlled on the heap and is not possible on the stack.

Куча

  • The heap contains a linked list of used and free blocks. New allocations on the heap (by new или же malloc) are satisfied by creating a suitable block from one of the free blocks. This requires updating list of blocks on the heap. This meta information about the blocks on the heap is also stored on the heap often in a small area just in front of every block.
  • As the heap grows new blocks are often allocated from lower addresses towards higher addresses. Thus you can think of the heap as a heap of memory blocks that grows in size as memory is allocated. If the heap is too small for an allocation the size can often be increased by acquiring more memory from the underlying operating system.
  • Allocating and deallocating many small blocks may leave the heap in a state where there are a lot of small free blocks interspersed between the used blocks. A request to allocate a large block may fail because none of the free blocks are large enough to satisfy the allocation request even though the combined size of the free blocks may be large enough. This is called heap fragmentation.
  • When a used block that is adjacent to a free block is deallocated the new free block may be merged with the adjacent free block to create a larger free block effectively reducing the fragmentation of the heap.

Куча

The stack

  • The stack often works in close tandem with a special register on the CPU named the stack pointer. Initially the stack pointer points to the top of the stack (the highest address on the stack).
  • The CPU has special instructions for pushing values onto the stack and popping them back from the stack. Each push stores the value at the current location of the stack pointer and decreases the stack pointer. A pop retrieves the value pointed to by the stack pointer and then increases the stack pointer (don't be confused by the fact that adding a value to the stack decreases the stack pointer and removing a value increases it. Remember that the stack grows to the bottom). The values stored and retrieved are the values of the CPU registers.
  • When a function is called the CPU uses special instructions that push the current instruction pointer, ie the address of the code executing on the stack. The CPU then jumps to the function by setting the instruction pointer to the address of the function called. Later, when the function returns, the old instruction pointer is popped from the stack and execution resumes at the code just after the call to the function.
  • When a function is entered, the stack pointer is decreased to allocate more space on the stack for local (automatic) variables. If the function has one local 32 bit variable four bytes are set aside on the stack. When the function returns, the stack pointer is moved back to free the allocated area.
  • Если функция имеет параметры, они помещаются в стек перед вызовом функции. Затем код в функции может перемещаться вверх по стеку от текущего указателя стека, чтобы найти эти значения.
  • Вызов функции вложенности работает как шарм. Каждый новый вызов будет выделять параметры функции, адрес возврата и пространство для локальных переменных, и эти записи активации могут быть сложены для вложенных вызовов и будут корректно разматываться при возврате функций.
  • Поскольку стек является ограниченным блоком памяти, вы можете вызвать переполнение стека, вызвав слишком много вложенных функций и / или выделив слишком много места для локальных переменных. Часто область памяти, используемая для стека, настраивается таким образом, что запись ниже дна (самый низкий адрес) стека вызовет ловушку или исключение в ЦП. Это исключительное условие может быть перехвачено средой выполнения и преобразовано в некое исключение переполнения стека.

Стек

Может ли функция быть размещена в куче вместо стека?

Нет, записи активации для функций (то есть локальных или автоматических переменных) размещаются в стеке, который используется не только для хранения этих переменных, но и для отслеживания вызовов вложенных функций.

Как управлять кучей, действительно зависит от среды выполнения. C использует malloc и C++ использует new, но у многих других языков есть сборка мусора.

Тем не менее, стек является более низкоуровневой функцией, тесно связанной с архитектурой процессора. Растить кучу, когда не хватает места, не так уж сложно, так как это может быть реализовано в вызове библиотеки, которая обрабатывает кучу. Однако увеличение стека часто невозможно, так как переполнение стека обнаруживается только тогда, когда это слишком поздно; и закрытие потока выполнения - единственно возможный вариант.

В следующем коде C#

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

Вот как управляется память

Изображение переменных в стеке

Local Variables это должно продолжаться до тех пор, пока вызов функции идет в стек. Куча используется для переменных, время жизни которых мы на самом деле не знаем заранее, но мы ожидаем, что они продлятся некоторое время. В большинстве языков очень важно знать во время компиляции, насколько велика переменная, если мы хотим сохранить ее в стеке.

Объекты (которые меняются по размеру по мере их обновления) попадают в кучу, потому что мы не знаем во время создания, как долго они будут длиться. Во многих языках куча мусора собирается для поиска объектов (таких как объект cls1), на которые больше нет ссылок.

В Java большинство объектов попадают прямо в кучу. В таких языках, как C / C++, структуры и классы часто могут оставаться в стеке, когда вы не имеете дело с указателями.

Более подробную информацию можно найти здесь:

Разница между выделением стека и кучи памяти "timmurphy.org

и здесь:

Создание объектов в стеке и куче

Эта статья является источником изображения выше: Шесть важных концепций.NET: стек, куча, типы значений, ссылочные типы, упаковка и распаковка - CodeProject

но имейте в виду, что он может содержать некоторые неточности.

Чтобы уточнить, этот ответ имеет неверную информацию ( Томас исправил свой ответ после комментариев, круто:)). Другие ответы просто не объясняют, что означает статическое распределение. Итак, ниже я объясню три основные формы распределения и их связь с кучей, стеком и сегментом данных. Я также покажу некоторые примеры на C/C++ и Python, чтобы помочь людям понять.

"Статические" (AKA статически размещенные) переменные не размещаются в стеке. Не думайте так - многие люди делают только потому, что "статический" очень похож на "стек". Они на самом деле не существуют ни в стеке, ни в куче. Они являются частью того, что называется сегментом данных.

Однако, как правило, лучше рассматривать " область действия " и " время жизни ", а не "стек" и "куча".

Область действия относится к тому, какие части кода могут обращаться к переменной. Обычно мы думаем о локальной области (доступ к которой может получить только текущая функция) в сравнении с глобальной областью действия (можно получить доступ в любом месте), хотя область действия может стать намного более сложной.

Время жизни относится к тому, когда переменная выделяется и освобождается во время выполнения программы. Обычно мы думаем о статическом распределении (переменная будет сохраняться в течение всей продолжительности программы, что делает ее полезной для хранения одной и той же информации при нескольких вызовах функций) в сравнении с автоматическим распределением (переменная сохраняется только во время одного вызова функции, что делает ее полезной для хранение информации, которая используется только во время вашей функции и может быть отброшена, когда вы закончите) в сравнении с динамическим распределением (переменные, продолжительность которых определяется во время выполнения, а не во время компиляции, как статические или автоматические).

Хотя большинство компиляторов и интерпретаторов реализуют это поведение аналогично с точки зрения использования стеков, куч и т. Д., Компилятор может иногда нарушать эти соглашения, если он хочет, пока поведение корректно. Например, из-за оптимизации локальная переменная может существовать только в регистре или может быть полностью удалена, даже если большинство локальных переменных существует в стеке. Как было отмечено в нескольких комментариях, вы можете свободно реализовать компилятор, который даже не использует стек или кучу, а вместо этого использует некоторые другие механизмы хранения (редко делается, поскольку стеки и кучи отлично подходят для этого).

Я приведу простой аннотированный C-код, чтобы проиллюстрировать все это. Лучший способ научиться - это запускать программу под отладчиком и наблюдать за ее поведением. Если вы предпочитаете читать Python, перейдите к концу ответа:)

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

Особенно острым примером того, почему важно различать время жизни и область видимости, является то, что переменная может иметь локальную область видимости, но статическое время жизни - например, "someLocalStaticVariable" в приведенном выше примере кода. Такие переменные могут сделать наши общие, но неформальные привычки именования очень запутанными. Например, когда мы говорим " локально ", мы обычно имеем в виду " локально распределенную переменную с автоматически распределенной областью ", а когда мы говорим "глобально", мы обычно имеем в виду " глобально распределенную переменную с статической привязкой ". К сожалению, когда речь заходит о таких вещах, как " статически распределенные переменные в области видимости файла ", многие люди просто говорят…

Некоторые из вариантов синтаксиса в C/C++ усугубляют эту проблему - например, многие люди думают, что глобальные переменные не являются "статическими" из-за синтаксиса, показанного ниже.

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

int main() {return 0;}

Обратите внимание, что добавление ключевого слова "static" в объявлении выше предотвращает глобальную область видимости var2. Тем не менее, глобальное var1 имеет статическое размещение. Это не интуитивно понятно! По этой причине я стараюсь никогда не использовать слово "статический" при описании области действия, а вместо этого говорю что-то вроде "файл" или "ограниченный файл" область действия. Однако многие люди используют фразу "статический" или "статический объем" для описания переменной, доступ к которой возможен только из одного файла кода. В контексте времени жизни "статический" всегда означает, что переменная выделяется при запуске программы и освобождается при выходе из программы.

Некоторые люди считают эти понятия специфичными для C/C++. Они не. Например, приведенный ниже пример Python иллюстрирует все три типа распределения (в интерпретируемых языках возможны некоторые тонкие различия, о которых я не буду здесь говорить).

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

Стек Когда вы вызываете функцию, в стек помещаются аргументы этой функции и некоторые другие накладные расходы. Некоторая информация (например, куда идти по возвращении) также хранится там. Когда вы объявляете переменную внутри вашей функции, эта переменная также размещается в стеке.

Выделение стека довольно просто, потому что вы всегда освобождаете его в обратном порядке. Стек вещи добавляются при вводе функций, соответствующие данные удаляются при выходе из них. Это означает, что вы склонны оставаться в небольшой области стека, если не вызываете множество функций, которые вызывают множество других функций (или не создаете рекурсивное решение).

Куча Куча - это общее имя, куда вы помещаете данные, которые вы создаете, на лету. Если вы не знаете, сколько космических кораблей собирается создать ваша программа, вы, вероятно, будете использовать новый (или malloc или эквивалентный) оператор для создания каждого космического корабля. Это распределение останется на некоторое время, поэтому, скорее всего, мы освободим вещи в другом порядке, чем мы их создали.

Таким образом, куча является гораздо более сложной, потому что в конечном итоге возникают области памяти, которые не используются, чередуются с фрагментами, которые - фрагментация памяти. Поиск свободной памяти нужного вам размера - сложная проблема. Вот почему следует избегать кучи (хотя она все еще часто используется).

Реализация Реализация как стека, так и кучи обычно зависит от времени выполнения / ОС. Часто игры и другие приложения, критичные по производительности, создают свои собственные решения для памяти, которые захватывают большой кусок памяти из кучи и затем распределяют ее изнутри, чтобы не полагаться на ОС для памяти.

Это практично только в том случае, если использование вашей памяти сильно отличается от нормы - то есть для игр, в которых вы загружаете уровень за одну огромную операцию и можете отбросить все это за другую огромную операцию.

Физическое расположение в памяти Это менее важно, чем вы думаете, из-за технологии, называемой виртуальной памятью, которая заставляет вашу программу думать, что у вас есть доступ к определенному адресу, где физические данные находятся где-то еще (даже на жестком диске!). Адреса, которые вы получаете для стека, расположены в порядке возрастания по мере того, как дерево вызовов становится глубже. Адреса для кучи непредсказуемы (т. Е. Специфичны для имплиментации) и, честно говоря, не важны.

Другие довольно хорошо ответили на широкие удары, поэтому я добавлю несколько деталей.

  1. Стек и куча не обязательно должны быть единичными. Распространенная ситуация, когда у вас есть более одного стека, если у вас есть более одного потока в процессе. В этом случае каждый поток имеет свой собственный стек. Вы также можете иметь более одной кучи, например, некоторые конфигурации DLL могут приводить к тому, что разные DLL распределяются из разных куч, поэтому обычно плохая идея освобождать память, выделенную другой библиотекой.

  2. В C вы можете получить выгоду от распределения переменной длины за счет использования alloca, которая выделяется в стеке, в отличие от alloc, которая выделяется в куче. Эта память не выдержит вашего оператора return, но она полезна для чистого буфера.

  3. Создание огромного временного буфера в Windows, который вы не используете, не является бесплатным. Это связано с тем, что компилятор будет генерировать цикл проверки стека, который вызывается при каждом входе в вашу функцию, чтобы убедиться, что стек существует (поскольку Windows использует одну защитную страницу в конце стека, чтобы определить, когда ему нужно увеличить стек. Если вы обращаетесь к памяти более чем на одну страницу с конца стека, вы потерпите крах). Пример:

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

Другие прямо ответили на ваш вопрос, но, пытаясь понять стек и кучу, я думаю, что полезно рассмотреть структуру памяти традиционного процесса UNIX (без потоков и mmap()распределители). На веб-странице Глоссарий по управлению памятью приведена схема этого макета памяти.

Стек и куча традиционно расположены на противоположных концах виртуального адресного пространства процесса. Стек обращается автоматически при обращении к нему до размера, установленного ядром (который можно настроить с помощью setrlimit(RLIMIT_STACK, ...)). Куча увеличивается, когда распределитель памяти вызывает brk() или же sbrk() системный вызов, отображающий больше страниц физической памяти в виртуальное адресное пространство процесса.

В системах без виртуальной памяти, таких как некоторые встроенные системы, часто применяется одна и та же базовая схема, за исключением того, что размер стека и кучи фиксирован. Однако в других встроенных системах (например, основанных на микроконтроллерах PIC от Microchip) программный стек представляет собой отдельный блок памяти, который не может быть адресован командами перемещения данных, и может быть изменен или прочитан только косвенно с помощью команд потока программы (вызов, возврат и т. д.). Другие архитектуры, такие как процессоры Intel Itanium, имеют несколько стеков. В этом смысле стек является элементом архитектуры ЦП.

Что такое стек?

Стек - это куча объектов, обычно аккуратно расположенных.

Стеки в вычислительных архитектурах - это области памяти, в которые данные добавляются или удаляются в порядке поступления.
В многопоточном приложении каждый поток будет иметь свой собственный стек.

Что такое куча?

Куча - это неопрятная коллекция беспорядочно скопившихся вещей.

В вычислительных архитектурах куча - это область динамически выделяемой памяти, которая автоматически управляется операционной системой или библиотекой диспетчера памяти.
Память в куче выделяется, освобождается и регулярно изменяется во время выполнения программы, и это может привести к проблеме, называемой фрагментацией.
Фрагментация происходит, когда объектам памяти выделяются небольшие промежутки между ними, которые слишком малы для хранения дополнительных объектов памяти.
Чистый результат представляет собой процент пространства кучи, который не может использоваться для дальнейшего выделения памяти.

Оба вместе

В многопоточном приложении каждый поток будет иметь свой собственный стек. Но все разные потоки будут разделять кучу.
Поскольку разные потоки совместно используют кучу в многопоточном приложении, это также означает, что должна быть некоторая координация между потоками, чтобы они не пытались получить доступ и манипулировать одними и теми же частями памяти в куче в в то же время.

Что быстрее - стек или куча? И почему?

Стек намного быстрее, чем куча.
Это связано с тем, как память выделяется в стеке.
Выделение памяти в стеке так же просто, как перемещение указателя стека вверх.

Для новичков в программировании, вероятно, будет хорошей идеей использовать стек, поскольку он проще.
Поскольку стек небольшой, его можно использовать, когда вы точно знаете, сколько памяти вам понадобится для ваших данных, или если вы знаете, что размер ваших данных очень мал.
Лучше использовать кучу, когда вы знаете, что вам понадобится много памяти для ваших данных, или вы просто не уверены, сколько памяти вам понадобится (например, с динамическим массивом).

Модель памяти Java

Стек - это область памяти, в которой хранятся локальные переменные (включая параметры метода). Когда дело доходит до объектных переменных, это просто ссылки (указатели) на реальные объекты в куче.
Каждый раз, когда создается экземпляр объекта, часть памяти кучи откладывается для хранения данных (состояния) этого объекта. Поскольку объекты могут содержать другие объекты, некоторые из этих данных могут фактически содержать ссылки на эти вложенные объекты.

Стек - это часть памяти, которой можно манипулировать с помощью нескольких инструкций языка ассемблера, таких как "pop" (удалить и вернуть значение из стека) и "push" (передать значение в стек), но также вызвать (вызвать подпрограмму - это толкает адрес для возврата в стек) и return (возврат из подпрограммы - это извлекает адрес из стека и переходит к нему). Это область памяти под регистром указателя стека, которая может быть установлена ​​по мере необходимости. Стек также используется для передачи аргументов подпрограммам, а также для сохранения значений в регистрах перед вызовом подпрограмм.

Куча - это часть памяти, которая предоставляется приложению операционной системой, обычно через системный вызов, подобный malloc. В современных ОС эта память представляет собой набор страниц, к которым имеет доступ только вызывающий процесс.

Размер стека определяется во время выполнения и, как правило, не увеличивается после запуска программы. В программе на Си стек должен быть достаточно большим, чтобы вместить каждую переменную, объявленную внутри каждой функции. Куча будет динамически расти по мере необходимости, но ОС в конечном итоге делает вызов (она часто будет увеличивать кучу больше, чем запрашивает malloc, так что по крайней мере некоторым будущим malloc не придется возвращаться к ядру, чтобы получить больше памяти. Такое поведение часто настраивается)

Поскольку вы выделили стек перед запуском программы, вам никогда не понадобится malloc, прежде чем вы сможете использовать стек, так что это небольшое преимущество. На практике очень трудно предсказать, что будет быстрым, а что медленным, в современных операционных системах с подсистемами виртуальной памяти, потому что то, как страницы реализованы и где они хранятся, является деталью реализации.

Я думаю, что многие другие люди дали вам в основном правильные ответы по этому вопросу.

Одна деталь, которая была упущена, однако, состоит в том, что "куча" на самом деле должна, вероятно, называться "бесплатным магазином". Причина этого различия заключается в том, что исходное бесплатное хранилище было реализовано со структурой данных, известной как "биномиальная куча". По этой причине выделение из ранних реализаций malloc()/free() было выделением из кучи. Однако в наши дни большинство бесплатных магазинов реализованы с очень сложными структурами данных, которые не являются биномиальными кучами.

Вы можете сделать некоторые интересные вещи со стеком. Например, у вас есть такие функции, как alloca (при условии, что вы можете обойти обильные предупреждения, касающиеся его использования), который является формой malloc, которая специально использует стек, а не кучу памяти.

Тем не менее, ошибки памяти на основе стека являются одними из худших, которые я испытал. Если вы используете кучную память и выходите за границы выделенного блока, у вас есть хороший шанс вызвать ошибку сегмента. (Не 100%: ваш блок может быть случайно смежным с другим, который вы ранее разместили.) Но так как переменные, созданные в стеке, всегда смежны друг с другом, выписывание границ может изменить значение другой переменной. Я узнал, что всякий раз, когда я чувствую, что моя программа перестала подчиняться законам логики, это, вероятно, переполнение буфера.

Просто в стеке создаются локальные переменные. Кроме того, каждый раз, когда вы вызываете подпрограмму, счетчик программы (указатель на следующую машинную инструкцию) и любые важные регистры, а иногда и параметры помещаются в стек. Затем любые локальные переменные внутри подпрограммы помещаются в стек (и используются оттуда). Когда подпрограмма заканчивается, все эти вещи возвращаются из стека. ПК и регистр данных получаются и возвращаются туда, где они были, как сейчас, чтобы ваша программа могла продолжать свой веселый путь.

Куча - это область динамического выделения памяти (явные вызовы "new" или "allocate"). Это специальная структура данных, которая может отслеживать блоки памяти разных размеров и их статус распределения.

В "классических" системах ОЗУ было расположено так, что указатель стека начинался с нижней части памяти, указатель кучи начинался сверху, и они росли друг к другу. Если они перекрываются, у вас недостаточно оперативной памяти. Это не работает с современными многопоточными ОС, хотя. Каждый поток должен иметь свой собственный стек, и они могут создаваться динамически.

Из WikiAnwser.

стек

Когда функция или метод вызывает другую функцию, которая в свою очередь вызывает другую функцию и т. Д., Выполнение всех этих функций приостанавливается до тех пор, пока самая последняя функция не вернет свое значение.

Эта цепочка приостановленных вызовов функций является стеком, поскольку элементы в стеке (вызовы функций) зависят друг от друга.

Стек важно учитывать при обработке исключений и выполнении потоков.

отвал

Куча - это просто память, используемая программами для хранения переменных. Элемент кучи (переменные) не имеет зависимостей друг от друга и всегда может быть доступен случайным образом в любое время.

стек

  • Очень быстрый доступ
  • Не нужно явно перераспределять переменные
  • Процессор эффективно управляет пространством, память не фрагментируется
  • Только локальные переменные
  • Ограничение на размер стека (зависит от ОС)
  • Переменные не могут быть изменены

отвал

  • Переменные могут быть доступны по всему миру
  • Нет ограничений на объем памяти
  • (Относительно) медленный доступ
  • Не гарантируется эффективное использование пространства, память может фрагментироваться со временем, так как блоки памяти выделяются, а затем освобождаются
  • Вы должны управлять памятью (вы отвечаете за распределение и освобождение переменных)
  • Переменные могут быть изменены с помощью realloc()

В сортировке

Стек используется для статического выделения памяти и куча для динамического выделения памяти, которые хранятся в оперативной памяти компьютера.


В деталях

Стек

Стек представляет собой структуру данных "LIFO" (последний пришел, первый вышел), которая управляется и оптимизируется центральным процессором достаточно близко. Каждый раз, когда функция объявляет новую переменную, она "помещается" в стек. Затем при каждом выходе из функции все переменные, помещенные в стек этой функцией, освобождаются (то есть они удаляются). Как только переменная стека освобождается, эта область памяти становится доступной для других переменных стека.

Преимущество использования стека для хранения переменных заключается в том, что память управляется за вас. Вам не нужно выделять память вручную или освобождать ее, если она вам больше не нужна. Более того, поскольку процессор эффективно организует стековую память, чтение и запись в переменные стека происходит очень быстро.

Больше можно найти здесь.


Куча

Куча - это область памяти вашего компьютера, которая не управляется автоматически и не так жестко управляется процессором. Это более свободно плавающая область памяти (и больше). Чтобы выделить память в куче, вы должны использовать malloc() или calloc(), которые являются встроенными функциями C. После выделения памяти в куче вы несете ответственность за использование free() для освобождения этой памяти, когда она вам больше не нужна.

Если вы этого не сделаете, ваша программа будет иметь то, что известно как утечка памяти. То есть память в куче все равно будет выделена (и не будет доступна другим процессам). Как мы увидим в разделе отладки, есть инструмент под названием Valgrind, который может помочь вам обнаружить утечки памяти.

В отличие от стека, куча не имеет ограничений по размеру переменного размера (кроме очевидных физических ограничений вашего компьютера). Память кучи немного медленнее для чтения и записи, потому что для доступа к памяти в куче нужно использовать указатели. Мы поговорим об указателях в ближайшее время.

В отличие от стека, переменные, созданные в куче, доступны любой функции в любой точке вашей программы. Переменные кучи по сути являются глобальными.

Больше можно найти здесь.


Переменные, размещенные в стеке, сохраняются непосредственно в памяти, и доступ к этой памяти очень быстрый, и его распределение решается при компиляции программы. Когда функция или метод вызывает другую функцию, которая в свою очередь вызывает другую функцию и т. Д., Выполнение всех этих функций приостанавливается до тех пор, пока самая последняя функция не вернет свое значение. Стек всегда резервируется в порядке LIFO, последний зарезервированный блок всегда является следующим блоком, который должен быть освобожден. Это действительно упрощает отслеживание стека, освобождение блока из стека - всего лишь настройка одного указателя.

Переменным, выделенным в куче, выделяется их память во время выполнения, и доступ к этой памяти немного медленнее, но размер кучи ограничен только размером виртуальной памяти. Элементы кучи не имеют зависимостей друг от друга и всегда могут быть доступны случайным образом в любое время. Вы можете выделить блок в любое время и освободить его в любое время. Это значительно усложняет отслеживание того, какие части кучи выделены или свободны в любой момент времени.

Вы можете использовать стек, если точно знаете, сколько данных вам нужно выделить до компиляции, и он не слишком велик. Вы можете использовать кучу, если вы точно не знаете, сколько данных вам понадобится во время выполнения или если вам нужно выделить много данных.

В многопоточной ситуации каждый поток будет иметь свой собственный полностью независимый стек, но они будут совместно использовать кучу. Стек зависит от потока, а куча зависит от приложения. Стек важно учитывать при обработке исключений и выполнении потоков.

Каждый поток получает стек, в то время как для приложения обычно есть только одна куча (хотя весьма часто иметь несколько куч для разных типов размещения).

Во время выполнения, если приложению требуется больше кучи, оно может выделять память из свободной памяти, а если стеку требуется память, оно может выделять память из свободной памяти, выделенной для приложения.

Даже более подробно здесь и здесь.


Теперь перейдите к ответам на ваш вопрос.

В какой степени они контролируются ОС или языковой средой выполнения?

ОС выделяет стек для каждого потока системного уровня при создании потока. Обычно ОС вызывается языковой средой выполнения для выделения кучи для приложения.

Больше можно найти здесь.

Какова их сфера применения?

Уже дано в топе.

"Вы можете использовать стек, если точно знаете, сколько данных вам нужно выделить до компиляции, и он не слишком велик. Вы можете использовать кучу, если точно не знаете, сколько данных вам потребуется во время выполнения или если вам нужно выделить много данных. "

Больше можно найти здесь.

От чего зависит размер каждого из них?

Размер стека устанавливается ОС при создании потока. Размер кучи устанавливается при запуске приложения, но может увеличиваться по мере необходимости в пространстве (распределитель запрашивает больше памяти у операционной системы).

Что делает быстрее?

Распределение стека происходит намного быстрее, поскольку все, что он на самом деле делает, - это перемещает указатель стека. Используя пулы памяти, вы можете получить сопоставимую производительность за счет выделения кучи, но это связано с небольшой дополнительной сложностью и собственными головными болями.

Кроме того, стек против кучи - это не только вопрос производительности; он также много говорит вам об ожидаемом времени жизни объектов.

Подробности можно найти здесь.

ОК, просто и в двух словах, они означают упорядоченный, а не заказанный...!

Стек: В элементах стека вещи оказываются друг на друге, что означает, что они будут быстрее и эффективнее обрабатываться!...

Так что всегда есть индекс, указывающий на конкретный элемент, также обработка будет быстрее, также есть связь между элементами!...

Куча: нет порядка, обработка будет медленнее, а значения перепутаны без какого-либо определенного порядка или индекса... есть случайные и нет никакой связи между ними... поэтому время выполнения и использования может варьироваться...

Я также создаю изображение ниже, чтобы показать, как они могут выглядеть:

стек, куча и данные каждого процесса в виртуальной памяти:

https://bayanbox.ir/view/581244719208138556/virtual-memory.jpg

В 1980-х годах UNIX распространялся как кролики, а крупные компании катались самостоятельно. У Exxon был один, так же как и десятки торговых марок, потерянные для истории. То, как память была размещена, оставалось на усмотрение многих разработчиков.

Типичная C-программа была размещена в памяти с возможностью увеличения путем изменения значения brk(). Как правило, HEAP была чуть ниже этого значения brk, а увеличение brk увеличивало количество доступной кучи.

Один STACK, как правило, представлял собой область ниже HEAP, которая представляла собой участок памяти, не содержащий ничего ценного, до вершины следующего фиксированного блока памяти. Этот следующий блок часто был CODE, который мог быть перезаписан данными стека в одном из известных хаков своей эпохи.

Одним типичным блоком памяти был BSS (блок нулевых значений), который случайно не был обнулен в предложении одного производителя. Другой был DATA, содержащий инициализированные значения, включая строки и числа. Третьим был CODE, содержащий CRT (время выполнения C), main, функции и библиотеки.

Появление виртуальной памяти в UNIX меняет многие ограничения. Нет объективной причины, по которой эти блоки должны быть смежными, фиксированными по размеру или заказанными определенным образом. Конечно, до UNIX были Multics, которые не страдали от этих ограничений. Вот схема, показывающая один из макетов памяти той эпохи.

Типичная схема памяти программ в стиле UNIX C 1980-х годов

  • Вступление

Физическая память - это диапазон физических адресов ячеек памяти, в которых приложение или система хранит свои данные, код и т. Д. Во время выполнения. Управление памятью означает управление этими физическими адресами путем обмена данными из физической памяти на запоминающее устройство и затем обратно в физическую память, когда это необходимо. ОС реализует сервисы управления памятью с использованием виртуальной памяти. Как разработчику приложений на C# вам не нужно писать никаких сервисов управления памятью. CLR использует базовые сервисы управления памятью ОС для предоставления модели памяти для C# или любого другого языка высокого уровня, нацеленного на CLR.

На рисунке 4-1 показана физическая память, которая была выделена и управляется ОС с использованием концепции виртуальной памяти. Виртуальная память - это абстрактный вид физической памяти, управляемый ОС. Виртуальная память - это просто серия виртуальных адресов, и эти виртуальные адреса при необходимости преобразуются ЦП в физический адрес.

Рисунок 4-1. Абстракция памяти CLR

CLR предоставляет абстрактный уровень управления памятью для виртуальной среды выполнения, используя сервисы оперативной памяти. К абстрактным понятиям, которые использует CLR, относятся AppDomain, thread, stack, heapmemorymapped file и т. Д. Концепция домена приложения (AppDomain) предоставляет вашему приложению изолированную среду выполнения.

  • Память Взаимодействие между CLR и ОС

Посмотрев трассировку стека при отладке следующего приложения на C# с использованием WinDbg, вы увидите, как CLR использует базовые службы управления памятью ОС (например, метод HeapFree из KERNEL32.dll, метод RtlpFreeHeap из ntdll.dll) для реализации собственная модель памяти:

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()); }
    }
}

Скомпилированная сборка программы загружается в WinDbg для начала отладки. Для инициализации сеанса отладки вы используете следующие команды:

0: 000> sxe ld clrjit

0: 000> г

0: 000>.loadby sos clr

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

Затем вы устанавливаете точку останова в методе Main класса Program с помощью команды! Bpmd:

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

Для продолжения выполнения и прерывания в точке останова выполните команду g:

0: 000> г

Когда выполнение прерывается в точке останова, вы используете команду! Ee stack для просмотра сведений трассировки стека всех потоков, запущенных для текущего процесса. Следующий вывод показывает трассировку стека для всех потоков, запущенных для приложения CH_04.exe:

0: 000>! Ee stack


Нить 0

Текущий кадр: (MethodDesc 00233800 +0 CH_04.Program.Main (System.String []))

ChildEBP RetAddr Caller, Callee

0022ed24 5faf21db clr! CallDescrWorker + 0x33

/ трассировка удалена /

0022f218 77712d68 ntdll! RtlFreeHeap + 0x142, вызов ntdll! RtlpFreeHeap

0022f238 771df1ac KERNEL32! HeapFree + 0x14, вызывая ntdll! RtlFreeHeap

0022f24c 5fb4c036 clr! EEHeapFree + 0x36, вызывая KERNEL32! HeapFree

0022f260 5fb4c09d clr! EEHeapFreeInProcessHeap + 0x24, вызов clr! EEHeapFree

0022f274 5fb4c06d Оператор clr! Delete[]+0x30, вызывая clr! EEHeapFreeInProcessHeap / trace remove / /

0022f4d0 7771316f ntdll! RtlpFreeHeap + 0xb7a, вызывающий ntdll! _SEH_epilog4

0022f4d4 77712d68 ntdll! RtlFreeHeap + 0x142, вызов ntdll! RtlpFreeHeap

0022f4f4 771df1ac KERNEL32! HeapFree + 0x14, вызывая ntdll! RtlFreeHeap

/ трассировка удалена /

Эта трассировка стека показывает, что CLR использует службы управления памятью ОС для реализации своей собственной модели памяти. Любая операция с памятью в.NET проходит через уровень памяти CLR на уровень управления памятью ОС.

Рисунок 4-2 иллюстрирует типичную модель памяти приложения C#, используемую CLR во время выполнения.

Рисунок 4-2. Типичная модель памяти приложения C#

Модель памяти CLR тесно связана со службами управления памятью ОС. Чтобы понять модель памяти CLR, важно понять базовую модель памяти ОС. Также важно знать, как адресное пространство физической памяти абстрагируется в адресное пространство виртуальной памяти, как виртуальное адресное пространство используется пользовательским приложением и системным приложением, как работает сопоставление виртуального адреса с физическим, как память файл работает и так далее. Эти базовые знания улучшат ваше понимание концепций модели памяти CLR, включая AppDomain, стек и кучу.

Для получения дополнительной информации обратитесь к этой книге:

Разобраться в C#: узнать, как работает C# в.NET Framework

Эта книга + ClrViaC# + Windows Internals - отличные ресурсы для углубленного изучения.net framework и связи с ОС.

Пара центов: я думаю, будет хорошо нарисовать память графически и более просто:

Это мое видение построения памяти процесса с упрощением для более легкого понимания происходящего


Стрелки - показывают, где растут стек и куча, размер стека процесса имеет ограничение, определенное в ОС, ограничения размера стека потока по параметрам в API создания потока обычно. Обычно размер кучи ограничивается максимальным размером виртуальной памяти процесса, например, для 32-битных 2-4 ГБ.

Так просто: куча процесса является общей для процесса и всех потоков внутри, используя для распределения памяти в общем случае что-то вроде malloc ().

Стек - это быстрая память для хранения в общем случае возвращаемых указателей и переменных функций, которые обрабатываются как параметры в вызове функции, локальные переменные функции.

У меня есть кое-что поделиться с вами, хотя основные моменты уже написаны.

стек

  • Очень быстрый доступ.
  • Хранится в оперативной памяти.
  • Здесь загружаются вызовы функций, а также передаются локальные переменные и параметры функций.
  • Пространство освобождается автоматически, когда программа выходит из области видимости.
  • Хранится в последовательной памяти.

отвал

  • Медленный доступ по сравнению со стеком.
  • Хранится в оперативной памяти.
  • Здесь хранятся динамически создаваемые переменные, что позже требует освобождения выделенной памяти после использования.
  • Хранится везде, где выполняется выделение памяти, доступ к нему осуществляется всегда по указателю.

Интересная заметка:

  • Если бы вызовы функций были сохранены в куче, это привело бы к двум беспорядочным точкам:
    1. Благодаря последовательному хранению в стеке выполнение выполняется быстрее. Хранение в куче привело бы к огромному расходу времени, в результате чего вся программа выполнялась медленнее.
    2. Если бы функции хранились в куче (грязное хранилище, на которое указывает указатель), не было бы никакого способа вернуться назад к адресу вызывающей стороны (какой стек дает из-за последовательного хранения в памяти).

Отзывы приветствуются.

Так как некоторые ответы стали придирчивыми, я собираюсь внести свою лепту.

Удивительно, но никто не упомянул, что несколько (то есть не связанных с количеством запущенных потоков на уровне ОС) стеков вызовов можно найти не только в экзотических языках (PostScript) или платформах (Intel Itanium), но также в волокнах, зеленых потоках. и некоторые реализации сопрограмм.

Волокна, зеленые нити и сопрограммы во многом похожи, что приводит к путанице. Разница между волокнами и зелеными нитями заключается в том, что первые используют кооперативную многозадачность, в то время как последние могут иметь либо кооперативную, либо вытесняющую (или даже обе). Различия между волокнами и сопрограммами см. Здесь.

В любом случае цель обоих волокон, зеленых нитей и сопрограмм состоит в том, чтобы несколько функций выполнялись одновременно, но не параллельно (см. Этот вопрос в разделе SO), в пределах одного потока уровня ОС, передавая управление вперед и назад друг от друга. организованно.

При использовании волокон, зеленых нитей или сопрограмм у вас обычно есть отдельный стек для каждой функции. (Технически, для каждой функции используется не просто стек, а целый контекст выполнения. Самое главное, регистры ЦП.) Для каждого потока имеется столько стеков, сколько имеется одновременно работающих функций, и поток переключается между выполнением каждой функции. в соответствии с логикой вашей программы. Когда функция запускается до конца, ее стек уничтожается. Таким образом, количество и время жизни стеков являются динамическими и не определяются количеством потоков на уровне ОС!

Обратите внимание, что я сказал, что " обычно есть отдельный стек для каждой функции". Существуют как стековые, так и не стековые реализации программ. Наиболее известными стековыми реализациями C++ являются Boost.Coroutine и Microsoft PPL. async/await, (Тем не менее, возобновляемые функции C++ (иначе " async а также await "), которые были предложены для C++17, могут использовать сопрограммы без стеков.)

Предложение Fibers к стандартной библиотеке C++ ожидается в ближайшее время. Также есть сторонние библиотеки. Зеленые темы чрезвычайно популярны в таких языках, как Python и Ruby.

Вот Это Да! Так много ответов, и я не думаю, что один из них понял это правильно...

1) Где и что они (физически в памяти реального компьютера)?

Стек - это память, которая начинается с наивысшего адреса памяти, выделенного для образа вашей программы, и затем уменьшается в этом значении. Он зарезервирован для параметров вызываемой функции и для всех временных переменных, используемых в функциях.

Есть две кучи: государственная и частная.

Частная куча начинается с 16-байтовой границы (для 64-битных программ) или 8-байтовой границы (для 32-битных программ) после последнего байта кода в вашей программе, а затем увеличивается в значении оттуда. Это также называется кучей по умолчанию.

Если частная куча становится слишком большой, она перекрывает область стека, как и стек, перекрывающий кучу, если она становится слишком большой. Поскольку стек начинается с более высокого адреса и работает до более низкого адреса, при правильном взломе вы можете сделать стек настолько большим, что он будет переполнять область приватной кучи и перекрывать область кода. Хитрость заключается в том, чтобы перекрыть достаточно области кода, которую вы можете подключить к коду. Это немного сложно сделать, и вы рискуете сбой программы, но это легко и очень эффективно.

Общедоступная куча находится в собственном пространстве памяти за пределами пространства образов вашей программы. Именно эта память будет перекачана на жесткий диск, если ресурсы памяти станут недостаточными.

2) В какой степени они контролируются ОС или языковой средой выполнения?

Стек управляется программистом, частная куча управляется ОС, а общедоступная куча никем не контролируется, потому что это служба ОС - вы делаете запросы, и они либо удовлетворяются, либо отклоняются.

2б) Какова их сфера применения?

Все они являются глобальными для программы, но их содержимое может быть частным, общедоступным или глобальным.

2с) От чего зависит размер каждого из них?

Размер стека и частная куча определяются параметрами времени выполнения вашего компилятора. Общая куча инициализируется во время выполнения с помощью параметра размера.

2d) Что делает быстрее?

Они не предназначены для быстрой работы, они предназначены для того, чтобы быть полезными. То, как их использует программист, определяет, являются ли они "быстрыми" или "медленными"

REF:

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

Где и какие они (физически в памяти реального компьютера)?

ОТВЕТ: Оба находятся в оперативной памяти.

В СТОРОНУ:

Оперативная память похожа на стол, а жесткие диски/твердотельные накопители (постоянное хранилище) — на книжные полки. Чтобы читать что-либо, у вас должна быть открыта книга на вашем столе, а у вас может быть столько открытых книг, сколько поместится на вашем столе. Чтобы получить книгу, вы берете ее с книжной полки и открываете на своем столе. Чтобы вернуть книгу, вы закрываете ее на столе и возвращаете на книжную полку.

Стек и куча — это имена, которые мы даем компиляторам для хранения разных типов данных в одном и том же месте (т. е. в ОЗУ).

Каков их масштаб?
Чем определяется размер каждого из них?
Что делает человека быстрее?

ОТВЕЧАТЬ:

  1. Стек предназначен для статических данных (фиксированного размера).

    а. Во время компиляции компилятор считывает типы переменных, используемые в вашем коде.

    я. Он выделяет фиксированный объем памяти для этих переменных.
    II. Этот размер этой памяти не может расти.

    б. Память непрерывна (один блок), поэтому доступ иногда быстрее, чем куча

    в. Объект, помещенный в стек, который во время выполнения увеличивается в памяти за пределы размера стека, вызывает ошибку переполнения стека .

  2. Куча предназначена для динамических (изменяющихся размеров) данных.

    а. Объем памяти ограничен только объемом свободного места в ОЗУ
    i. Используемая сумма может увеличиваться или уменьшаться по мере необходимости во время выполнения.

    б. Поскольку элементы распределяются в куче путем поиска свободного места везде, где оно существует в ОЗУ, данные не всегда находятся в непрерывном разделе, что иногда делает доступ медленнее, чем доступ к стеку.

    в. Программисты вручную помещают элементы в стек с newключевое слово и ДОЛЖНЫ вручную освободить эту память, когда они закончат ее использовать.
    я. Код, который повторно выделяет новую память, не освобождая ее, когда она больше не нужна для утечки памяти.

В СТОРОНУ:

Стек и куча были введены не для повышения скорости; они были введены для обработки переполнения памяти. Первая проблема, связанная с использованием стека по сравнению с кучей, должна заключаться в том, не произойдет ли переполнение памяти. Если объект предназначен для увеличения в размере до неизвестной величины (например, связанный список или объект, элементы которого могут содержать произвольный объем данных), поместите его в кучу. Насколько это возможно, используйте контейнеры vector, map и list стандартной библиотеки C++ (STL), поскольку они эффективно используют память и скорость и добавляются для облегчения вашей жизни (вам не нужно беспокоиться о выделении/освобождении памяти).

После запуска кода, если вы обнаружите, что он работает неприемлемо медленно, вернитесь и проведите рефакторинг кода и посмотрите, можно ли его запрограммировать более эффективно. Может оказаться, что проблема вообще не имеет ничего общего со стеком или кучей (например, используйте итеративный алгоритм вместо рекурсивного, посмотрите на задачи ввода-вывода по сравнению с задачами, связанными с процессором, возможно, добавьте многопоточность или многопроцессорность).

Я говорю иногда медленнее/быстрее выше, потому что скорость программы может не иметь никакого отношения к элементам, выделенным в стеке или куче.

В какой степени они контролируются операционной системой или средой выполнения языка?

ОТВЕЧАТЬ:

  • Размер стека определяется компилятором во время компиляции.

  • Размер кучи изменяется во время выполнения.(Куча работает с ОС во время выполнения для выделения памяти. )

В СТОРОНУ:

Ниже немного больше об управлении и операциях времени компиляции и времени выполнения.

Каждый компьютер имеет уникальную архитектуру набора команд (ISA) , которая представляет собой его аппаратные команды (например, «ПЕРЕМЕЩЕНИЕ», «ПРЫЖОК», «ДОБАВИТЬ» и т. д.).

  • ОС — это не что иное, как диспетчер ресурсов (контролирует, как, когда и где использовать память, процессоры, устройства и информацию).

  • ISA ОС называется пустой машиной, а остальные команды называются расширенной машиной. Ядро — это первый уровень расширенной машины. Он контролирует такие вещи, как

    • определение того, какие задачи используют процессор (планировщик),
    • сколько памяти или сколько аппаратных регистров выделить задаче (диспетчеру) и
    • порядок выполнения задач (регулировщик).
  • Когда мы говорим «компилятор», мы обычно имеем в виду компилятор, ассемблер и компоновщик вместе.

    • Компилятор превращает исходный код в язык ассемблера и передает его ассемблеру,
    • Ассемблер превращает язык ассемблера в машинный код (команды ISA) и передает его компоновщику.
    • Компоновщик берет весь машинный код (возможно, сгенерированный из нескольких исходных файлов) и объединяет его в одну программу.
  • Машинный код передается ядру при выполнении, которое определяет, когда оно должно запускаться и брать на себя управление, но сам машинный код содержит команды ISA для запроса файлов, запроса памяти и т. д. Таким образом, код выдает команды ISA, но все должно пройти по ядру.

Многие ответы верны в качестве концепций, но мы должны отметить, что аппаратному обеспечению (т.е. микропроцессору) необходим стек, чтобы разрешить вызывать подпрограммы (CALL на ассемблере..). (ООП ребята будут называть это методами)

В стеке вы сохраняете обратные адреса, а вызов → push / ret → pop управляется напрямую аппаратно.

Вы можете использовать стек для передачи параметров... даже если он медленнее, чем использование регистров (скажет гуру микропроцессора или хорошая книга по BIOS 1980-х годов...)

  • Без стека ни один микропроцессор не может работать. (мы не можем представить программу, даже на ассемблере, без подпрограмм / функций)
  • Без кучи это может. (Программа на языке ассемблера может работать без, так как куча - это понятие ОС, как malloc, то есть вызов OS/Lib.

Использование стека происходит быстрее:

  • Это аппаратное обеспечение, и даже push/pop очень эффективны.
  • malloc требует входа в режим ядра, использования блокировки / семафора (или других примитивов синхронизации), выполнения некоторого кода и управления некоторыми структурами, необходимыми для отслеживания распределения.

Я чувствую, что большинство ответов очень запутанные и технические, в то время как я не нашел ни одного, который мог бы просто объяснить причины, лежащие в основе этих двух концепций (т.е. почему люди создали их в первую очередь?) и почему вас это должно волновать. Вот моя попытка:

Данные в стеке являются временными и автоматически очищаются

Данные в куче являются постоянными до тех пор, пока не будут удалены вручную

Вот и все.

 

 

Тем не менее, для дополнительных пояснений:

Стек предназначен для использования в качестве эфемерной или рабочей памяти , области памяти , которая, как мы знаем, будет регулярно полностью удаляться, независимо от того, какой беспорядок мы туда вносим во время жизни нашей программы. Это как памяткана своем столе, на котором вы делаете какие-то пометки, которые приходят вам в голову и которые едва ли могут показаться вам важными, и которые, как вы знаете, вы просто выбросите в конце дня, потому что вы отфильтровали и систематизировали настоящие важные заметки на другом носителе, как документ или книга. Мы не заботимся о презентации, зачеркиваниях или неразборчивом тексте, это просто для нашей работы дня, и мы будем помнить, что мы имели в виду час или два назад, это просто наш быстрый и грязный способ хранить идеи, которые мы хотим запомнить. позже, не нарушая наш текущий поток мыслей. Вот что люди подразумевают под «стеком — это блокнот ».

Куча , однако, является долговременной памятью , фактически важным документом.что мы будем хранить, консультироваться и зависеть в течение очень долгого времени после его создания. Следовательно, он должен иметь идеальную форму и строго содержать важные данные. Вот почему его изготовление стоит дорого, и его нельзя использовать для варианта использования нашего прецедентного меморандума. Было бы нецелесообразно, или даже просто бесполезно, делать все мои записи в презентации академической статьи, написав текст как каллиграфию. Однако эта презентация чрезвычайно полезна для хорошо отобранных данных. Вот что такое куча. Хорошо известные данные, важные для жизненного цикла приложения, которые хорошо контролируются и необходимы во многих местах вашего кода. Таким образом, система никогда не удалит эти ценные данные без вашего явного запроса, потому что она знает, что «вот где важные данные!».

Вот почему вам нужно управлять и заботиться о распределении памяти в куче, но не нужно возиться с этим для стека.

Большинство популярных ответов - это просто технические детали фактической реализации этой концепции на реальных компьютерах.

Итак, что нужно вынести из этого:

Неважные, работающие, временные данные, необходимые только для того, чтобы наши функции и объекты работали, (как правило) более уместны для хранения в стеке.

Важные, постоянные и базовые данные приложения (как правило) более уместно хранить в куче.

Об этом, конечно, нужно думать только в контексте жизненного цикла вашей программы. Фактически важные для человека данные, сгенерированные вашей программой, очевидно, должны быть сохранены во внешнем файле. (Поскольку, будь то куча или стек, они оба полностью очищаются при завершении вашей программы.)

PS: Это всего лишь общие правила, вы всегда можете найти крайние случаи, и каждый язык имеет свою собственную реализацию и вытекающие из этого особенности, это следует рассматривать как руководство к концепции и эмпирическое правило.

Куча - это область динамически выделяемой памяти, которой автоматически управляет операционная система или библиотека диспетчера памяти. Вы можете выделить блок в любое время и освободить его в любое время. Выделение кучи требует ведения полной записи о том, какая память выделена, а какая нет, а также некоторых накладных расходов для уменьшения фрагментации, поиска смежных сегментов памяти, достаточно больших, чтобы соответствовать запрошенному размеру, и т. Память можно освободить в любой момент, оставив свободное место. По мере роста кучи новые блоки часто распределяются с более низких адресов на более высокие. Таким образом, вы можете думать о куче как о куче блоков памяти, размер которой увеличивается по мере выделения памяти. Если куча слишком мала для выделения, размер часто можно увеличить, получив больше памяти из базовой операционной системы.Память, выделенная из кучи, будет оставаться выделенной до тех пор, пока не произойдет одно из следующих событий:

  • Память освобождена
  • Программа завершается

Стек:

  • Хранится в оперативной памяти компьютера как в куче.
  • Переменные, созданные в стеке, выходят за рамки и автоматически освобождаются.
  • Гораздо быстрее выделять по сравнению с переменными в куче.
  • Хранит локальные данные, адреса возврата, используемые для передачи параметров.
  • Может иметь место переполнение стека, когда используется слишком большая часть стека (в основном из-за бесконечной или слишком глубокой рекурсии, очень больших выделений).
  • Вы могли бы использовать стек, если точно знаете, сколько данных вам нужно выделить до времени компиляции, и он не слишком велик.
  • Обычно максимальный размер уже определяется при запуске вашей программы.

Куча:

  • Хранится в оперативной памяти компьютера как в стеке.
  • В C++ переменные в куче должны быть уничтожены вручную и никогда не выпадают из области видимости.
  • Данные освобождаются с помощью delete, delete[] или free.
  • Медленнее выделяется по сравнению с переменными в стеке.
  • Используется по запросу для выделения блока данных для использования программой.
  • Может иметь место фрагментация при большом количестве распределений и освобождений.
  • В C++ или C данные, созданные в куче, будут указываться указателями и выделены с помощью new или malloc соответственно.
  • Могут происходить сбои выделения, если запрашивается слишком большой буфер.
  • Вы могли бы использовать кучу, если не знаете точно, сколько данных вам понадобится во время выполнения или если вам нужно выделить много данных.
  • Ответственный за утечки памяти.

Стек - это, по сути, легкодоступная память, которая просто управляет своими элементами как - хорошо - стеком. В стопку могут попасть только предметы, размер которых известен заранее. Это относится к числам, строкам и логическим значениям.

Кучи память для элементов которого вы не можете € ™ т предопределяют точный размер и структура. Поскольку объекты и массивы могут видоизменяться и изменяться во время выполнения, они должны попадать в кучу.

Источник: Academind

Другие вопросы по тегам