Что и где находится стек и куча? - PullRequest
7640 голосов
/ 17 сентября 2008

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

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

Ответы [ 25 ]

5632 голосов
/ 17 сентября 2008

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

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

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

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

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

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

Каков их охват?

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

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

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

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

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

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

2232 голосов
/ 17 сентября 2008

Stack:

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

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;
1320 голосов
/ 19 марта 2009

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

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

    Stack like a stack of papers

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

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

    Heap like a heap of licorice allsorts

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

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

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

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

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

  • Каков их охват?

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

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

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

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

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

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

693 голосов
/ 31 июля 2009

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

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

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

Куча

  • Куча содержит связанный список используемых и свободных блоков. Новые выделения в куче (на new или malloc) выполняются путем создания подходящего блока из одного из свободных блоков. Это требует обновления списка блоков в куче. Эта метаинформация о блоках в куче также хранится в куче часто в небольшой области перед каждым блоком.
  • По мере роста кучи новые блоки часто распределяются от более низких адресов к более высоким адресам. Таким образом, вы можете думать о куче как о куче блоков памяти, размер которых увеличивается с выделением памяти. Если куча слишком мала для выделения, ее размер часто можно увеличить, получая больше памяти из базовой операционной системы.
  • Распределение и освобождение многих небольших блоков может оставить кучу в состоянии, когда между используемыми блоками расположено множество небольших свободных блоков. Запрос на выделение большого блока может потерпеть неудачу, поскольку ни один из свободных блоков не является достаточно большим, чтобы удовлетворить запрос на выделение, даже если объединенный размер свободных блоков может быть достаточно большим. Это называется фрагментация кучи .
  • Когда используемый блок, который находится рядом со свободным блоком, освобождается, новый свободный блок может быть объединен с соседним свободным блоком, чтобы создать больший свободный блок, эффективно уменьшающий фрагментацию кучи.

The heap

стек

  • Стек часто работает в тесном тандеме со специальным регистром на процессоре, называемым указатель стека . Первоначально указатель стека указывает на вершину стека (самый высокий адрес в стеке).
  • ЦПУ имеет специальные инструкции для помещения значений в стек и извлечения их обратно из стека. Каждый push сохраняет значение в текущем местоположении указателя стека и уменьшает указатель стека. pop получает значение, на которое указывает указатель стека, а затем увеличивает указатель стека (не смущайтесь тем фактом, что добавление значения в стек уменьшается указатель стека и удаление значение увеличивает . Помните, что стек увеличивается до дна). Сохраненные и извлеченные значения являются значениями регистров ЦП.
  • Когда вызывается функция, центральный процессор использует специальные инструкции, которые выдвигают указатель текущей инструкции , то есть адрес кода, выполняемого в стеке. Затем процессор переходит к функции, устанавливая указатель инструкции на адрес вызываемой функции. Позже, когда функция возвращается, старый указатель инструкции извлекается из стека, и выполнение возобновляется в коде сразу после вызова функции.
  • Когда функция введена, указатель стека уменьшается, чтобы выделить больше места в стеке для локальных (автоматических) переменных. Если функция имеет одну локальную 32-битную переменную, в стеке выделяются четыре байта. Когда функция возвращается, указатель стека перемещается назад, чтобы освободить выделенную область.
  • Если функция имеет параметры, они помещаются в стек перед вызовом функции. Затем код в функции может перемещаться вверх по стеку от текущего указателя стека, чтобы найти эти значения.
  • Вызов функции вложенности работают как шарм. Каждый новый вызов будет выделять параметры функции, адрес возврата и пространство для локальных переменных, и эти записи активации могут быть сложены для вложенных вызовов и будут корректно разматываться при возврате функций.
  • Поскольку стек является ограниченным блоком памяти, вы можете вызвать переполнение стека , вызвав слишком много вложенных функций и / или выделив слишком много места для локальных переменных. Часто область памяти, используемая для стека, настраивается таким образом, что запись ниже дна (самый низкий адрес) стека вызовет ловушку или исключение в ЦП. Это исключительное условие может быть перехвачено средой выполнения и преобразовано в какое-то исключение переполнения стека.

The stack

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

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

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

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

386 голосов
/ 09 ноября 2012

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

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

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

Picture of variables on the stack

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

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

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

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

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

и здесь:

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

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

но учтите, что в нем могут быть некоторые неточности.

197 голосов
/ 17 сентября 2008

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

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

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

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

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

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

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

178 голосов
/ 11 ноября 2012

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

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

Однако, как правило, лучше рассматривать « scope » и « life », а не «stack» и «heap».

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

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

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

Я приведу простой аннотированный 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» в приведенном выше примере кода. Такие переменные могут сделать наши общие, но неформальные привычки именования очень запутанными. Например, когда мы говорим « local », мы обычно имеем в виду « локально распределенную автоматически назначаемую переменную », а когда мы говорим «global», мы обычно имеем в виду « глобально ограниченную статически распределенную переменную ». К сожалению, когда речь заходит о таких вещах, как " статически распределенные переменные в пределах файла ", многие просто говорят ... " да ??? ".

Некоторые из вариантов синтаксиса в 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 имеет статическое размещение. Это не интуитивно понятно! По этой причине я стараюсь никогда не использовать слово «статический» при описании области действия, а вместо этого говорю что-то вроде «файл» или «ограниченный файл» область действия. Однако многие люди используют фразу «статический» или «статический объем» для описания переменной, доступ к которой возможен только из одного файла кода. В контексте времени жизни «static» всегда означает, что переменная выделяется при запуске программы и освобождается при выходе из программы.

Некоторые люди считают эти понятия специфичными для 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
161 голосов
/ 17 сентября 2008

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

  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.
}
128 голосов
/ 17 сентября 2008

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

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

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

109 голосов
/ 17 сентября 2008

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

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

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

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

...