Распределение памяти: стек против кучи?


84

Меня путают основы распределения памяти между стеком и кучей . Согласно стандартному определению (то, что все говорят), все типы значений будут размещены в стеке, а ссылочные типы будут помещены в кучу .

Теперь рассмотрим следующий пример:

class MyClass
{
    int myInt = 0;    
    string myString = "Something";
}

class Program
{
    static void Main(string[] args)
    {
       MyClass m = new MyClass();
    }
}

Теперь, как будет происходить выделение памяти в C #? Будет ли объект MyClass(то есть m) полностью размещен в куче? То есть int myIntи string myStringв кучу пойдут оба?

Или объект будет разделен на две части и будет размещен в обоих местах памяти, то есть в стеке и куче?


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

Отвечает ли это на ваш вопрос? Что и где стопка и куча?
Olivier Rogier

Ответы:


55

mразмещается в куче, включая myInt. Ситуации, когда примитивные типы (и структуры) размещаются в стеке, возникают во время вызова метода, который выделяет место для локальных переменных в стеке (потому что это быстрее). Например:

class MyClass
{
    int myInt = 0;

    string myString = "Something";

    void Foo(int x, int y) {
       int rv = x + y + myInt;
       myInt = 2^rv;
    }
}

rv, x, yВсе будет в стеке. myIntнаходится где-то в куче (и должен быть доступен через thisуказатель).


7
Важное добавление - помнить, что «стек» и «куча» на самом деле являются деталями реализации в .NET. Вполне возможно создать легальную реализацию C #, которая вообще не использует распределение на основе стека.
JSB ձոգչ

5
Я согласен, что к ним следует относиться таким образом, но не совсем верно, что это просто детали реализации. Это явно указано в общедоступной документации API и в языковом стандарте (EMCA-334, ISO / IEC 23270: 2006) (т. Е. «Значения структур хранятся« в стеке ». Внимательные программисты могут иногда повысить производительность за счет разумного использования структур. ") Но да, если скорость выделения кучи является узким местом для вашего приложения, вы, вероятно, делаете это неправильно (или используете неправильный язык).
Mud

65

Вы должны рассмотреть вопрос о том, где размещаются объекты, в качестве детали реализации. Для вас не имеет значения, где именно хранятся биты объекта. Может иметь значение, является ли объект ссылочным типом или типом значения, но вам не нужно беспокоиться о том, где он будет храниться, пока вы не начнете оптимизацию поведения сборки мусора.

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

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

Я что-то пропустил?

Конечно, было бы упущением, если бы я не дал ссылку на сообщения Эрика Липперта по этой теме:


1
Эд: Когда именно это имеет значение?
Гейб

1
@Gabe: имеет значение, где хранятся биты. Например, если вы отлаживаете аварийный дамп, вы не продвинетесь далеко, если не знаете, где искать объекты / данные.
Брайан Расмуссен,

14
Вы пропустили следующие ситуации: если тип значения взят из неуправляемого кода, доступ к которому осуществляется через небезопасный указатель, то он, возможно, не находится ни в стеке, ни в управляемой куче. Он может находиться в неуправляемой куче или в какой-либо структуре данных, которая даже не является кучей. Сама идея, что существует «куча» - тоже миф. Куч может быть несколько десятков. Кроме того, если дрожание решает зарегистрировать значение, то оно не в стеке или куче, а в регистре.
Эрик Липперт

1
Вторая часть Эрика Липперта была фантастическим чтением, спасибо за ссылку!
Дэн Бечард

2
Это важно, потому что об этом спрашивают в интервью, но не в реальной жизни. :)
Mayank

23

«Все типы VALUE будут размещены в стеке» - это очень и очень неправильно; Переменные структуры могут находиться в стеке как переменные метода. Однако поля в типе живут с этим типом . Если тип объявления поля является классом, значения находятся в куче как часть этого объекта. Если тип объявления поля является структурой, поля являются частью этой структуры, где бы она ни находилась.

Даже переменные метода могут находиться в куче, если они захвачены (лямбда / анон-метод), или являются частью (например) блока итератора.


1
И не забывайте бокс: если у вас есть object x = 12;метод, 12 будут сохранены в куче, даже если это целое число (тип значения).
Гейб

@Gabe: места хранения значений типа содержат в себе поля (общедоступные и частные) типа значения. Места хранения ссылочного типа либо содержат null, либо ссылку на объект кучи соответствующего типа. Для каждого типа значения существует соответствующий тип объекта-кучи; попытка сохранить тип значения в месте хранения ссылочного типа создаст новый объект соответствующего ему типа объекта кучи, скопирует все поля в этот новый объект и сохранит ссылку на объект в месте хранения ссылочного типа. C # делает вид, что тип значения и тип объекта совпадают, но ...
supercat

... такая точка зрения скорее добавляет путаницы, чем понимания. Распакованный объект, List<T>.Enumeratorкоторый хранится в переменной этого типа, будет демонстрировать семантику значения, потому что это тип значения. Однако A, List<T>.Enumeratorкоторый хранится в переменной типа IEnumerator<T>, будет вести себя как ссылочный тип. Если рассматривать последних как тип, отличный от первого, различие в поведении легко объяснимо. Притворяться, что они одного типа, гораздо сложнее рассуждать о них.
supercat

12

2

Стек

Это stackблок памяти для хранения local variablesи parameters. Стек логически увеличивается и уменьшается при входе в функцию и выходе из нее.

Рассмотрим следующий метод:

public static int Factorial (int x)
{
    if (x == 0) 
    {
        return 1;
    }

    return x * Factorial (x - 1);
}

Этот метод рекурсивен, то есть вызывает сам себя. Каждый раз, когда вводится метод, в стеке выделяется новый int , и каждый раз, когда метод завершается, int освобождается .


Куча

  • Куча - это блок памяти, в котором objects(т. reference-type instancesЕ.) Находятся. Каждый раз, когда создается новый объект, он выделяется в куче, и возвращается ссылка на этот объект. Во время выполнения программы куча начинает заполняться по мере создания новых объектов. Среда выполнения имеет сборщик мусора, который периодически освобождает объекты из кучи, поэтому ваша программа не запускается Out Of Memory. Объект имеет право на освобождение, как только на него не ссылается что-либо само по себе alive.
  • Куча тоже хранит static fields. В отличие от объектов, размещенных в куче (которые могут собираться сборщиком мусора) these live until the application domain is torn down,.

Рассмотрим следующий метод:

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder ref1 = new StringBuilder ("object1");
        Console.WriteLine (ref1);
        // The StringBuilder referenced by ref1 is now eligible for GC.

        StringBuilder ref2 = new StringBuilder ("object2");
        StringBuilder ref3 = ref2;
        // The StringBuilder referenced by ref2 is NOT yet eligible for GC.
        Console.WriteLine (ref3); // object2
    }
}    

В приведенном выше примере мы начинаем с создания объекта StringBuilder, на который ссылается переменная ref1, а затем записываем его содержимое. Этот объект StringBuilder сразу же получает право на сборку мусора, потому что впоследствии его ничто не использует. Затем мы создаем еще один StringBuilder, на который ссылается переменная ref2, и копируем эту ссылку в ref3. Несмотря на то, что после этого момента ref2 не используется, ref3 сохраняет тот же объект StringBuilder живым, гарантируя, что он не станет подходящим для сбора, пока мы не закончим использование ref3.

Экземпляры типов значений (и ссылки на объекты) живут везде, где была объявлена ​​переменная. Если экземпляр был объявлен как поле внутри типа класса или как элемент массива, этот экземпляр живет в куче.


1

простые меры

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

Итак, лучше понять, как работает значение и ссылочный тип. Тип значения будет скопирован по значению, что означает, что когда вы передаете тип значения в качестве параметра в ФУНКЦИЮ, чем он будет скопирован по своей природе, это означает, что у вас будет полная новая копия .

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

так что в вашем случае

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

Я бы посоветовал вам начать читать блоги, написанные Эриком Липпертсом.

Блог Эрика


1

Каждый раз, когда в нем создается объект, он попадает в область памяти, известную как куча. Примитивные переменные, такие как int и double, выделяются в стеке, если они являются переменными локального метода, и в куче, если они являются переменными-членами. В методах локальные переменные помещаются в стек при вызове метода, а указатель стека уменьшается после завершения вызова метода. В многопоточном приложении каждый поток будет иметь свой собственный стек, но совместно использовать одну и ту же кучу. Вот почему в вашем коде следует проявлять осторожность, чтобы избежать проблем с одновременным доступом в пространстве кучи. Стек является потокобезопасным (каждый поток будет иметь свой собственный стек), но куча не является потокобезопасной, если не защищена синхронизацией через ваш код.

Эта ссылка также полезна http://www.programmerinterview.com/index.php/data-structures/difference-between-stack-and-heap/


0

m - это ссылка на объект MyClass, поэтому m хранится в стеке основного потока, а объект MyClass хранится в куче. Поэтому myInt и myString хранятся в куче. Обратите внимание, что m является только ссылкой (адресом памяти) и находится в основном стеке. когда m освобожден, GC очищает объект MyClass из кучи. Подробнее читайте все четыре части этой статьи https://www.c-sharpcorner.com/article/C-Sharp-heaping-vs-stacking-in-net- часть-я /

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.