Виділення пам'яті: стек проти купи?


83

Я плутаюся з основами розподілу пам'яті між Stack і Heap . Відповідно до стандартної роздільної здатності (те, що всі говорять), усі типи значень будуть виділені в стек, а типи посилань перейдуть у Купу .

Тепер розглянемо такий приклад:

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обидва підуть на купу?

Або об’єкт буде розділений на дві частини і буде розподілений в обидва місця пам’яті, тобто Stack і Heap?


Я проголосував просто тому, що хоча ці давно безглузді висловлювання є помилковими, я вважаю, що будуть хороші відповіді. Однак досить легко придумати тривіальні контраргументи проти запропонованого "подвійного розподілу" (підказка: об'єкт класу може - і багато хто це часто може - жити через межі викликів функцій).

Це відповідає на ваше запитання? Що і де є стек і купа?
Олів'є Рож'є

Відповіді:


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) (тобто "Значення структури зберігаються" у стеку ". Ретельні програмісти іноді можуть підвищити продуктивність завдяки розумному використанню структур. ") Але, так, якщо швидкість розподілу купи є вузьким місцем для вашої програми, ви, мабуть, робите це неправильно (або використовуєте неправильну мову).
Грязь

65

Ви повинні розглянути питання про те, де об’єкти виділяються як деталь реалізації. Для вас не має значення, де саме зберігаються біти об’єкта. Може мати значення, є об’єкт посилальним типом або типом значення, але вам не доведеться турбуватися про те, де він буде зберігатися, поки не почнеться оптимізувати поведінку збору сміття.

Хоча посилальні типи завжди виділяються в купі в поточних реалізаціях, типи значень можуть бути виділені в стеку - але це не обов'язково. Тип значення виділяється в стеку лише тоді, коли це невпакована локальна або тимчасова змінна, що не екранується, яка не міститься у типі посилання та не виділяється в реєстрі.

  • Якщо тип значення є частиною класу (як у вашому прикладі), він опиниться в купі.
  • Якщо він упакований, він опиниться в купі.
  • Якщо він знаходиться в масиві, він опиниться в купі.
  • Якщо це статична змінна, вона опиниться в купі.
  • Якщо його схопить закриття, воно опиниться на купі.
  • Якщо він використовується в ітераторі або асинхронному блоці, він опиниться в купі.
  • Якщо він створений небезпечним або некерованим кодом, його можна розподілити у будь-якому типі структури даних (не обов'язково в стеку чи купі).

Щось я пропустив?

Звичайно, я був би відхилений, якби не посилався на дописи Еріка Ліпперта на тему:


1
Ед: Коли саме це має значення?
Гейб,

1
@Gabe: Важливо, де зберігаються біти. Наприклад, якщо ви налагоджуєте дамп аварійного завершення роботи, ви не зайдете далеко, якщо не знаєте, де шукати об’єкти / дані.
Брайан Расмуссен,

14
Ви пропустили такі ситуації: якщо тип значення походить від некерованого коду, доступ до якого здійснюється через небезпечний покажчик, тоді він, можливо, не знаходиться ні в стеку, ні в керованій купі. Це може бути в некерованій купі або в якійсь структурі даних, яка навіть не є купою. Вся думка про те, що існує "купа", теж є міфом. Купи може бути десятками. Крім того, якщо джиттер вирішить зареєструвати значення, тоді його немає в стеку або купі, він знаходиться в реєстрі.
Ерік Ліпперт,

1
Частина друга Еріка Ліпперта була фантастичним читанням, дякую за посилання!
Dan Bechard,

1
Це важливо, оскільки про це запитують в інтерв’ю, але не в реальному житті. :)
Mayank

22

"Усі типи VALUE будуть виділені в стек" дуже, дуже неправильно; змінні struct можуть жити у стеку як змінні методу. Однак поля типу живуть із цим типом . Якщо тип оголошення поля є класом, значення знаходяться в купі як частина цього об'єкта. Якщо тип оголошення поля є структурою, поля є частиною цієї структури, де б і не існувала ця структура.

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


1
І не забувайте про бокс: якщо у вас є object x = 12;метод, 12 буде зберігатися в купі, хоча це ціле число (тип значення).
Гейб,

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

... така точка зору додає замішання, аніж розуміння. Безкошовий пакет, List<T>.Enumeratorякий зберігається у змінній цього типу, матиме семантику значення, оскільки це тип значення. Однак, 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

прості заходи

Тип значення можна вказати на СТЕКІ, це деталь реалізації, яку він може віднести до певної футуристичної структури даних.

отже, краще зрозуміти, як працює значення та тип посилання. Тип значення буде скопійовано за значенням, що означає, що коли ви передаєте тип значення як параметр у FUNCTION, ніж він буде скопійований природою, це означає, що ви отримаєте повну нову копію .

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

так у вашому випадку

myInt - це int, який інкапсулюється в клас, який виходить за межі посилального типу, тому він буде прив'язаний до екземпляра класу, який зберігатиметься в "HEAP".

я б запропонував, ви можете почати читати блоги, написані ERIC LIPPERTS.

Блог Еріка


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.