Введение¶
Программа без данных - ничто, и все современные языки программирования хранят данные в одном из двух мест: стеке вызовов и куче (иногда также в регистрах процессора, но мы не будем здесь вдаваться в подробности). Однако каждый язык считывает и записывает данные немного по—разному, иногда совсем по-другому. Итак, в следующих разделах мы объясним, как Mojo управляет памятью в ваших программах и как это влияет на способ написания кода Mojo.
Чтобы получить альтернативное представление о праве собственности в Mojo, ознакомьтесь с нашим постом в блоге, состоящим из двух частей: (Что такое право собственности на самом деле: подход к ментальной модели)[https://www.modular.com/blog/what-ownership-is-really-about-a-mental-model-approach] и (глубокое погружение в право собственности в Mojo)[https://www.modular.com/blog/deep-dive-into-ownership-in-mojo].
Обзор стека и кучи¶
Как правило, все современные языки программирования делят память запущенной программы на четыре сегмента:
- Текст. Скомпилированная программа.
- Данные. Глобальные данные, как инициализированные, так и неинициализированные.
- Стек. Локальные данные, автоматически управляемые во время выполнения программы.
- Куча. Динамически распределяемые данные, управляемые программистом.
Текстовые сегменты и сегменты данных имеют статический размер, но размер стека и кучи изменяется по мере выполнения программы.
В стеке хранятся данные, локальные для текущей функции. При вызове функции программа выделяет блок памяти — фрейм стека — именно такого размера, который требуется для хранения данных функции, включая любые локальные переменные фиксированного размера. Когда вызывается другая функция, новый стековый фрейм помещается на вершину стека. Когда функция выполняется, ее стековый фрейм удаляется из стека.
Обратите внимание, что мы сказали, что в стеке хранятся только "локальные значения фиксированного размера". Значения динамического размера, размер которых может изменяться во время выполнения, вместо этого хранятся в куче, которая представляет собой гораздо большую область памяти, позволяющую выделять динамическую память. Технически, локальная переменная для такого значения все еще хранится в стеке вызовов, но ее значение является указателем фиксированного размера на реальное значение в куче. Рассмотрим строку Mojo: она может быть любой длины, и ее длина может изменяться во время выполнения. Таким образом, структура Mojo String включает в себя несколько полей статического размера, а также указатель на динамически выделяемый буфер, содержащий фактические строковые данные.
Еще одно важное различие между кучей и стеком заключается в том, что управление стеком осуществляется автоматически — код для перемещения и извлечения фреймов стека добавляется компилятором. С другой стороны, кучная память управляется программистом, который явно выделяет и освобождает память. Вы можете сделать это косвенно, используя стандартные библиотечные типы, такие как List и String, или напрямую, используя UnsafePointer API.
Значения, которые должны пережить время существования функции (например, массив, передаваемый между функциями и не подлежащий копированию), хранятся в куче, поскольку кучная память доступна из любой точки стека вызовов, даже после того, как функция, которая ее создала, удалена из стека. Именно в таких ситуациях, когда выделенное в куче значение используется несколькими функциями, возникает большинство ошибок с памятью, и именно здесь стратегии управления памятью больше всего различаются в зависимости от языка программирования.
Стратегии управления памятью¶
Поскольку объем памяти ограничен, важно, чтобы программы удаляли неиспользуемые данные из кучи ("освобождали" память) как можно быстрее. Выяснить, когда следует освободить эту память, довольно сложно.
Некоторые языки программирования пытаются скрыть от вас сложности управления памятью, используя процесс "сборщика мусора", который отслеживает все использование памяти и периодически освобождает неиспользуемую память в куче (также известный как автоматическое управление памятью). Существенным преимуществом этого метода является то, что он избавляет разработчиков от необходимости вручную управлять памятью, что, как правило, позволяет избежать большего количества ошибок и повысить производительность работы разработчиков. Однако это приводит к снижению производительности, поскольку сборщик мусора прерывает выполнение программы и может не очень быстро освобождать память.
Другие языки требуют, чтобы вы вручную освобождали данные, размещенные в куче. При правильном выполнении это ускоряет выполнение программ, поскольку сборщик мусора не тратит время на обработку. Однако проблема такого подхода заключается в том, что программисты допускают ошибки, особенно когда нескольким частям программы требуется доступ к одной и той же памяти — становится трудно определить, какая часть программы "владеет" данными и должна их освободить. Программисты могут случайно освободить данные до того, как программа завершит работу с ними (что приведет к ошибкам "использования после освобождения"), или они могут освободить их дважды (ошибки "двойного освобождения"), или они могут никогда не освободить их (ошибки "утечки памяти"). Подобные ошибки могут привести к катастрофическим последствиям для программы, и эти ошибки часто трудно отследить, поэтому особенно важно, чтобы они не возникали в первую очередь.
Mojo использует третий подход, называемый "владение", который основан на наборе правил, которым программисты должны следовать при передаче значений. Правила гарантируют, что для данного значения одновременно может быть только один "владелец". Когда время жизни значения заканчивается, Mojo вызывает его деструктор, который отвечает за освобождение любой памяти в куче, которая должна быть освобождена.
Таким образом, Mojo помогает обеспечить освобождение памяти, но делает это детерминированным способом, защищенным от таких ошибок, как использование после освобождения, двойное освобождение и утечки памяти. Кроме того, он обеспечивает очень низкую нагрузку на производительность.
Модель владения ценностями Mojo обеспечивает превосходный баланс производительности программирования и надежной защиты памяти. Для этого требуется только изучить новый синтаксис и несколько правил совместного использования доступа к памяти в рамках вашей программы.
Но прежде чем мы объясним правила и синтаксис модели владения ценностями Mojo, вам сначала нужно понять семантику значений.