Как работает управление памятью в C?

Как работает управление памятью в C?
Как работает управление памятью в C?

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

Типы значений и ссылочные типы

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

Типы значений - это примитивные типы с фиксированными размерами, такие как

int

,

bool

,float

,

double

и т. д. Они передаются по значению, то есть если вы вызываете

someFunction(int arg)

аргумент копируется и отправляется как новое место в памяти.

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

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

Структура данных «последний пришел - первый ушел» (LIFO)
Структура данных «последний пришел - первый ушел» (LIFO)

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

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

Кадры стека определяют блоки памяти для различных функций. Распределение стека происходит очень быстро
Кадры стека определяют блоки памяти для различных функций. Распределение стека происходит очень быстро

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

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

Ссылочные типы, такие как экземпляры классов, часто инициализируются ключевым словом

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

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

Выделение кучи также происходит медленнее, чем выделение стека
Выделение кучи также происходит медленнее, чем выделение стека

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

  • Внешние переменные лямбда-функций, локальные переменные блоков

    IEnumerator

    и локальные переменные

    асинхронных методов хранятся в куче.

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

Наиболее заметным исключением из правила «ссылочные типы, находящиеся в куче» является использование

stackalloc

с

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

Что такое сборка мусора?

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

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

Сборщик мусора работает в фоновом потоке и периодически сканирует ваше приложение на наличие ссылок, которых больше нет в стеке
Сборщик мусора работает в фоновом потоке и периодически сканирует ваше приложение на наличие ссылок, которых больше нет в стеке

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

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