понедельник, 3 января 2022 г.

Память в .NET - что куда идет

 

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

Что в переменной?

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

Значение переменной ссылочного типа всегда либо ссылка, либо nullЕсли это ссылка, это должна быть ссылка на объект, совместимый с типом переменной. Например, переменная, объявленная как Stream sвсегда, будет иметь значение, которое является либо nullссылкой на экземпляр Streamкласса, либо ссылкой на него (Обратите внимание, что экземпляр подкласса Stream, например FileStream, также является экземпляром Stream.) Слот памяти, связанный с переменной, - это просто размер ссылки, каким бы большим ни был фактический объект, на который она ссылается. (Например, в 32-разрядной версии .NET слот переменной ссылочного типа всегда составляет всего 4 байта.)

Значение типа значения - это всегда данные для экземпляра самого типа. Например, предположим, что у нас есть структура, объявленная как:

struct PairOfInts
{
    public  int a;
    public  int b;
}

Значение переменной, объявленной как PairOfInts pairсама пара целых чисел, а не ссылка на пару целых чисел. Слот памяти достаточно велик, чтобы вместить оба целых числа (поэтому он должен быть 8 байтов). Обратите внимание, что переменная типа значения никогда не может иметь значение null- это не имело бы никакого смысла, как nullи концепция ссылочного типа, означающая, что «значение этой переменной ссылочного типа вообще не является ссылкой на какой-либо объект».

Так где же хранятся вещи?

Слот памяти для переменной хранится либо в стеке, либо в куче. Это зависит от контекста, в котором он объявлен:

  • Каждая локальная переменная (т. Е. Объявленная в методе) хранится в стеке. Это включает переменные ссылочного типа - сама переменная находится в стеке, но помните, что значение переменной ссылочного типа является только ссылкой (или null), а не самим объектом. Параметры метода также считаются локальными переменными, но если они объявлены с refмодификатором, они не получают своего собственного слота, но совместно используют слот с переменной, используемой в вызывающем коде. Подробнее см. Мою статью о передаче параметров .
  • Переменные экземпляра для ссылочного типа всегда находятся в куче. Вот где «живет» сам объект.
  • Переменные экземпляра для типа значения хранятся в том же контексте, что и переменная, объявляющая тип значения. Слот памяти для экземпляра фактически содержит слоты для каждого поля в экземпляре. Это означает (с учетом двух предыдущих пунктов), что структурная переменная, объявленная внутри метода, всегда будет в стеке, тогда как структурная переменная, которая является полем экземпляра класса, будет находиться в куче.
  • Каждая статическая переменная хранится в куче, независимо от того, объявлена ​​ли она в ссылочном типе или типе значения. Всего существует только один слот, независимо от того, сколько экземпляров создано. (Тем не менее, не нужно создавать никаких экземпляров для существования этого единственного слота.) Подробная информация о том, в какой именно куче находятся переменные, сложна, но подробно объясняется в статье MSDN по этому вопросу .

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

Наработанный пример

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

используя Систему;

struct PairOfInts
{
    статический  счетчик int = 0;
    
    public  int a;
    public  int b;
    
    внутренний PairOfInts ( int x, int y)
    {
        а = х;
        b = y;
        счетчик ++;
    }
}

класс Test
{
    PairOfInts pair;
    имя строки ;
    
    Тест (PairOfInts p, строка s, int x)
    {
        пара = р;
        name = s;
        пара. a + = x;
    }
    
    статическая  пустота Main ()
    {
        PairOfInts z = новый PairOfInts (1, 2);
        Тест t1 = новый тест (z, «первый» , 1);
        Тест t2 = новый тест (z, «второй» , 2);
        Тест t3 = null ;
        Тест t4 = t1;
        // XXX
    }
}

Посмотрим, что где в памяти на строчке, помеченной комментарием «XXX». (Предположим, что сборщик мусора ничего не делает.)

  • В PairOfIntsстеке есть экземпляр, соответствующий переменной zВнутри этого экземпляра a=1и b=2(8-байтовый слот, необходимый для zсебя, может быть представлен в памяти как 01 00 00 00 02 00 00 00.)
  • В Testстеке есть ссылка, соответствующая переменной t1Эта ссылка относится к экземпляру в куче, который занимает «что-то вроде» 20 байтов: 8 байтов информации заголовка (которая есть у всех объектов кучи), 8 байтов для PairOfIntsэкземпляра и 4 байта для ссылки на строку. («Что-то вроде» возникает из-за того, что в спецификации не говорится, как это должно быть организовано, или какого размера заголовок и т. Д.) Значение pairпеременной в этом экземпляре будет иметь a=2и b=2(возможно, представленное в памяти как 02 00 00 00 02 00 00 00). Ценностьnameпеременная в этом экземпляре будет ссылкой на строковый объект (который также находится в куче) и который (возможно, через другие объекты, такие как массив символов) представляет последовательность символов в слове «первый».
  • В Testстеке есть вторая ссылка, соответствующая переменной t2Эта ссылка относится ко второму экземпляру в куче, который очень похож на описанный выше, но со ссылкой на строку, представляющую «второй», а не «первый», и со значением pairwhere a=3 (поскольку было добавлено 2 к исходному значению 1). Если PairOfIntsбы тип был ссылочным, а не типом значения, то во всей программе был бы только один его экземпляр и всего несколько ссылок на единственный экземпляр, но на самом деле существует несколько экземпляров, каждый с разными значениями внутри.
  • В Testстеке есть третья ссылка, соответствующая переменной t3Эта ссылка null- она ​​не относится ни к одному из экземпляров Test(Есть некоторая двусмысленность в отношении того, считается ли это Testссылкой или нет - на самом деле это не имеет никакого значения - я обычно думаю, nullчто это ссылка, которая не ссылается на какой-либо объект, а не отсутствие ссылки Спецификация языка Java дает довольно приятную терминологию, говоря, что ссылка является либо nullуказателем, либо указателем на объект соответствующего типа.)
  • В Testстеке есть четвертая ссылка, соответствующая переменной t4Эта ссылка относится к тому же экземпляру, что и t1- т. Е. Значения t1и t4совпадают. Изменение значения одной из этих переменных не приведет к изменению значения другой, но изменение значения в объекте, на который они оба ссылаются с использованием одной ссылки, сделает это изменение видимым через другую ссылку. (Например, если вы установили, а t1.name="third";затем проверили t4.name, вы обнаружите, что это тоже относится к «третьему».)
  • Наконец, есть PairOfInts.counterпеременная, которая находится в куче (поскольку она статична). Для переменной есть только один «слот», сколь бы много (или мало) PairOfInts значений ни было.

Вернуться на главную страницу.

Комментариев нет:

Отправить комментарий

Паттерн 'Репозиторий' в ASP.NET

  Последнее обновление: 1.11.2015         Одним из наиболее часто используемых паттернов при работе с данными является паттерн 'Репозито...