Значимые и ссылочные типы

Значимые и ссылочные типы

Dmitriy Makarov

Я так часто слышал вопрос:

-"А знаете ли вы чем отличаются ссылочные типы от значимых?"

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

Начнем с простой теории что относится к значимым, а что относится к ссылочным типам:

Значимые:

  • Целочисленные типы (sbyte, byte, char, short, ushort, int, uint, long, ulong)
  • Типы с плавающей точкой (float, double)
  • 128 - разрядный тип данных (decimal)
  • Логический тип (bool)
  • Тип пользовательских структуры (struct)
  • Типы перечислений (enum)
  • Типы кортежей (ValueTuple)
  • Типы значений, допускающие NULL (Nullable<T>)
  • Тип символа (char)

Ссылочные:

  • Тип класс (class)
  • Тип интерфейс (interface)
  • Тип делегат (delegate)
  • Тип запись (record)
  • Тип динамик (dynamic)
  • Тип строка (string)
  • Тип объект (object)

Отличие этих двух видов типов заключается в том что значимый хранится в стеке(stack), а ссылочный в куче(heap). Что же вся эта куча воды значит, давайте разбираться на простом примере:

Выделение памяти в стеке.

1) Сначала мы попадаем в метод Main в котором есть метод StackExample.

2) Как только отладчик зашел в StackExamle выделяется память в стеке.

3) Когда отладчик видит переменную и знак = идет присвоение значения в стек.

4) В конце метода стек очищается и все три переменные значимого типа перестают существовать в стеке.

Теперь пример с элементом из кучи, я добавил класс MyInt и заменил 3 переменную на него.

Выделение памяти в стеке и куче.

1) Сначала мы попадаем в метод Main в котором есть метод StackExample.

2) Как только отладчик зашел в StackExamle выделяется память в стеке.

3) Когда отладчик видит переменную и знак = идет присвоение значения в стек.

4) Но на строке 16 есть ключевое слово new которое означает что память нужно выделить из кучи.

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

Для лучшего понимания я запилил gif:

Пошаговое выполнение и изменение стека и кучи.


Подводные камни или что может пойти не так как задумано

  • StackOverflowException

У стека ограниченный размер для 32-битных это 1 Mb для 64-битных это 4 Mb.

Самый простой пример того что память в стеке закончилась это зацикленная рекурсия:

StackOverflowException

Что произошло? Закончилась память в стеке, по сколько при каждом вызове метода StackOverflow мы будем добавлять в стек фрейм для этого метода с переменной и так до бесконечности (ну как до бесконечности в моем случае пока 4 Mb не заполнились).

  • OutOfMemoryException

Все наши компьютеры ограничены ресурсами и важно понимать что объекты динамической памяти(кучи) имеют ограничение в виде размера ОЗУ.
Сделаем пример который может привести к данному поведению:

OutOfMemoryException

Каждую интеракцию этого цикла в куче будет создаваться новый объект, каждый из этих объектов будет 32 byte, допустим у нас 1 Gb что позволит нам разместить 33 554 432, обычно такая ситуация получается когда мы пытаемся загрузить построчно файл и записываем его в string(очень распространённая задача в онлайн кодинге, обычно дают код и спрашивают что здесь не так).


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

Report Page