Использование памяти, стек и куча: основы управления памятью
В предыдущем разделе мы узнали, что в абстракции ISA (набора инструкций аппаратной части), "состояние" программы полностью представлено регистрами и памятью (хотя это не совсем верно, но вам сейчас не о чем беспокоиться). Это означает, что их содержимое полностью определяет поведение программы. И поскольку память гораздо больше регистров, она является основным хранилищем, где программа хранит свое состояние.
Однако ISA не предоставляют указаний о том, как следует использовать эту память. Хотя это добавляет гибкости, это также добавляет сложности. Обычно программам нужно знать, какой адрес памяти хранит какое значение; если она предполагает, что определенный адрес хранит определенное значение, но этот адрес был фактически перезаписан предыдущими инструкциями, то программа не будет работать как ожидалось.
С увеличением размера программного обеспечения становится все сложнее отслеживать, какой адрес хранит какое значение вручную, и убедиться, что чтение и запись в память работают правильно. Это называется проблемой управления памятью.
Чтобы систематически решить эту проблему, люди предложили множество различных моделей "как использовать память", таких как владение, автоматическое сборка мусора, RAII (ресурс ожидает инициализации) и т. д. Мы не будем описывать их все здесь, но мы опишем общие концепции, которыми почти все они пользуются или которые они разрабатывают. Эти концепции - это использование памяти, стек и куча.
Важно отметить, что эти концепции часто абстрагируются и не являются явными в языках более высокого уровня, особенно в языках сборщиков мусора, таких как Python. Однако понимание этих концепций является хорошей основой для понимания того, как программа выполняется на низком уровне (то же самое с ISA).
Использование памяти
Использование памяти помогает ответить на вопрос: для определенного участка памяти (т. е. ячеек памяти, соответствующих набору адресов памяти), на определенный период времени, какими способами он должен использоваться? То есть для чего эта часть памяти предназначена в каждый момент времени?
Абстракция используется как использование памяти
Если вы вспомните определение абстракции из предыдущего модуля, вы увидите, что использование памяти также является абстракцией: использование памяти определяет, что что-то (память) такое (что вы можете использовать для определенного использования в течение определенного периода времени) для кого-то (программиста), игнорируя все неважные детали.
Хотя это может показаться очевидной идеей, использование памяти в отдельности помогает решить множество проблем в управлении памятью, поскольку оно четко определяет, что "должно" и "не должно" происходить с адресом памяти.
Прежде чем определить использование части памяти, программисты смотрели на участок памяти и просто не знали, что с ним делать: Могу ли я записать в него данные? Какое значение хранится здесь? Это просто случайные байты, которые я могу перезаписать, или значащие содержимое, которые я должен сохранить? Перезапишет ли этот участок памяти какая-то последующая инструкция? Если я хочу сохранить что-то долговечное здесь, оно будет перезаписано в какой-то момент? С увеличением размера программы ответить на эти вопросы становится все сложнее, так как это требует просмотра каждой инструкции в программе и просмотра, что она делает с памятью.
Использование памяти значительно облегчает задачу. После того, как известно использование части памяти, разработчики могут делать некоторые предположения о допустимых способах ее использования и о том, что произойдет, если она будет использоваться определенным образом. Например, разработчик может подумать: "Хорошо, в этот момент времени эта часть памяти не содержит ничего важного, и поэтому я могу записать сюда что угодно. Однако, после завершения операции A, операция B будет использовать эту память для хранения своих данных, и все, что я запишу здесь, будет перезаписано, поэтому я должен убедиться, что я не буду читать эту часть памяти после того, как начнется операция B."
Некоторые термины, связанные с использованием памяти
Ниже описаны некоторые термины, связанные с использованием памяти, с которыми вы встретитесь в мире разработчиков.
Выделение памяти (Allocate)
"Выделение" участка памяти означает нахождение "свободной" памяти (т. е. участка памяти, который никто не использует) и назначение его для некоторого использования. После выделения участка памяти для некоторого использования, считается неправильным использовать его для чего-либо еще.
Например, в компьютерных программах обычно выделяется память для хранения данных, таких как день рождения кого-то.
Освобождение памяти (Deallocate)
"Освобождение" участка памяти означает пометку конца текущего использования (чего бы это ни было), связанного с ним. После того, как участок памяти будет освобожден, считается нормальным использовать его для любого другого использования, и неприемлемым использовать его для предыдущего использования.
Например, если участок памяти в настоящее время хранит день рождения кого-то и он будет освобожден, тогда его можно использовать для хранения результата \(153 \times 13251\) и т. д.
Термин «освобождение» иногда используется в качестве синонима «освобождение», или «уничтожение».
Стек и куча
Основываясь на концепции использования памяти, стек и куча - это два особенных участка памяти, создающие более четкую модель для управления памятью. Стек и куча не предназначены для конкретного использования памяти (например, для хранения дня рождения кого-то); скорее, они являются "пулами", из которых можно выделять память определенным образом.
Вы можете представить себе стек и кучу как существующие на протяжении всего существования программы.
Стек и куча в качестве структур данных
Стек и куча также являются названиями структур данных. Хотя они имеют некоторые сходства с представленными здесь объявлениями памяти, это не одно и то же.
Для уточнения разницы между "объектом памяти стека" и "структурой данных стека", первый обычно называется "памятью стека", а последний просто "стек". Аналогично, "объект памяти кучи" и "структура данных кучи" обычно называются "памятью кучи" и просто "кучей".
Стек
В терминах использования памяти, стек - это особый, обычно меньший участок памяти, который используется для хранения (приблизительно) "маленьких вещей, к которым требуется быстрый доступ".
Стек упорядочен по принципу последним пришел - первым вышел (LIFO, Last-In-First-Out), что означает, что при выделении памяти из него, новоиспеченная память находится наверху (т. е. ее адрес всегда предшествует или следует за адресом последней выделенной памяти). Аналогично, при освобождении памяти из него, самая верхняя память освобождается первой.
Почему стек хорош для "маленьких вещей, к которым требуется быстрый доступ"?
Поскольку стек обычно небольшой, он может хранить только маленькие вещи. С другой стороны, стек хорош для вещей, к которым требуется быстрый доступ, потому что он упорядочен: пока вы знаете размеры вещей после вещи, которую вы хотите получить доступ к ней, и адрес памяти верха стека (т.е. адрес последней вещи в стеке), вы можете легко вычислить адрес памяти вещи, к которой вы хотите получить доступ, и получить к ней доступ. В компилируемых языках, таких как C++, такие вычисления адресов памяти обычно выполняются статически во время компиляции, что означает, что при выполнении программы вы получаете адрес всего, к чему хотите обратиться в стеке, практически без вычислений (пока что вам не нужно понимать, как это работает; просто имейте в виду, что доступ к вещам в стеке легко и быстро).
Интересный факт
Стек маленький и его размер обычно фиксирован на протяжении всей жизни программы; когда вы пытаетесь использовать больше памяти, чем может предоставить стек, вы запускаете исключение, называемое «переполнение стека», которое кстати также является названием платформы, на которой разработчики обсуждают ошибки программы и их исправления.
Куча
В терминах использования памяти, куча - это особый, обычно больший участок памяти, который используется для хранения (приблизительно) "больших структур данных, которые долго существуют". Куча обычно намного больше стека.
Куча неупорядочена, что затрудняет как выделение, так и доступ к памяти по сравнению со стеком. В стеке память всегда выделяется сверху; в куче, однако, сначала вы должны найти свободный участок памяти, а затем выделить его; когда вы хотите получить доступ к чему-либо, вы должны знать его адрес памяти, иначе у вас будет очень трудности в поиске его в куче.
В компьютерных программах обычно используется типичная комбинация, когда что-то очень большое хранится в куче, но его адрес памяти хранится в стеке. Таким образом, вы можете хранить большие вещи (такие как базы данных, потому что куча большая), но также получать к ним относительно быстрый доступ (потому что стек быстрый).
Заключение
В этом разделе мы рассмотрели основные модели управления памятью, которые представлены использованием памяти, стеком и кучей.
Основные моменты:
- Использование памяти определяет, для чего служит определенная часть памяти в каждый момент времени.
- Стек - это небольшая, упорядоченная часть памяти, из которой быстро и легко получить доступ и выделить память.
- Куча - это неупорядоченная часть памяти. По сравнению со стеком, выделение памяти из кучи происходит медленнее, но куча намного больше стека.
Примеры AI Prompt
Если вас интересуют темы этого раздела, не стесняйтесь обратиться к ИИ, такому как ChatGPT, для получения дополнительной информации.
Вот некоторые примеры запросов, чтобы начать:
- Пожалуйста, объясните разницу между стеком и кучей памяти.