Що і де стопку і купу?


8099

Книги мов програмування пояснюють, що типи значень створюються на стеці , а типи посилань створюються на купі , не пояснюючи, що це за дві речі. Я не читав чіткого пояснення цього. Я розумію, що таке стек . Але,

  • Де і що вони (фізично в реальній пам'яті комп'ютера)?
  • Наскільки вони контролюються ОС або мовою роботи?
  • Яка сфера їх застосування?
  • Від чого залежить розмір кожного з них?
  • Що робить одного швидше?

175
тут дійсно хороше пояснення можна знайти тут Яка різниця між стеком і купою?
Songo

12
Також (справді) добре: codeproject.com/Articles/76153/… (частина стека / купи)
Бен


3
Пов’язані, див. Збіг між стеками . Виправлення зіткнення стека вплинуло на деякі аспекти системних змінних та поведінку rlimit_stack. Також дивіться випуск
jww

3
@mattshane Визначення стека та купи не залежать від значення та типових типів. Іншими словами, стек і купа можуть бути повністю визначені, навіть якщо типи значень та еталон ніколи не існували. Далі, розуміючи значення та типи посилань, стек - це лише деталь реалізації. Пер Ерік Ліпперт: Стек - це деталізація реалізації, перша частина .
Матвій

Відповіді:


5961

Стек - це пам'ять, відведена як подряпина для потоку виконання. Коли викликається функція, блок резервується у верхній частині стека для локальних змінних та деяких даних бухгалтерського обліку. Коли ця функція повертається, блок стає невикористаним і може бути використаний наступного разу, коли функція буде викликана. Стек завжди зарезервований у порядку LIFO (останній у першому виході); останній зарезервований блок - це завжди наступний блок, який буде звільнений. Це робить насправді просто стежити за стеком; звільнення блоку зі стека - це не що інше, як коригування одного вказівника.

Купа - це пам'ять, відведена для динамічного розподілу. На відміну від стека, немає примусового шаблону для розподілу та розподілу блоків з купи; ви можете виділити блок у будь-який час та звільнити його в будь-який час. Це робить набагато складнішим відстежувати, які частини купи виділяються чи вільні в будь-який момент часу; Для налаштування продуктивності нагромадження для різних моделей використання доступно багато спеціальних розподільників купи.

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

Щоб відповісти на ваші запитання безпосередньо:

Наскільки вони контролюються ОС або мовою виконання?

ОС створює стек для кожного потоку на рівні системи під час створення потоку. Зазвичай ОС викликається мовою виконання, щоб виділити купу для програми.

Яка сфера їх застосування?

Стек прикріплюється до нитки, тому коли нитка виходить з стека, вона відновлюється. Купа, як правило, виділяється при запуску програми під час виконання програми, і відтворюється, коли програма (технічно процес) закінчується.

Від чого залежить розмір кожного з них?

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

Що робить одного швидше?

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

Ясна демонстрація:
Джерело зображення: vikashazrati.wordpress.com


74
Хороша відповідь - але я думаю, ви повинні додати, що поки стек виділяється ОС, коли процес починається (припускаючи існування ОС), він підтримується вбудованим програмою. Це ще одна причина, коли стек швидше, а також операції натискання та попси - це звичайно одна інструкція, і сучасні машини можуть виконувати принаймні 3 з них за один цикл, тоді як виділення або звільнення купи передбачає введення коду ОС.
sqykly

275
Мене справді бентежить діаграма в кінці. Я думав, що отримав це, поки не побачив цього зображення.
Sina Madani

10
@Anarelle процесор виконує інструкції з осі або без нього. Прикладом, близьким моєму серцю, є SNES, який не мав жодних викликів API, жодної ОС, як ми це знаємо сьогодні, - але він мав стек. Виділення в стеку - це додавання і віднімання в цих системах, і це добре для змінних, зруйнованих при їх вискакуванні, повертаючись з функції, яка їх створила, але обмежувати це, скажімо, конструктором, результатом якого не може бути просто викинути. Для цього нам потрібна купа, яка не прив’язана до дзвінка та повернення. Більшість ОС мають API-купи, без причин робити це самостійно
sqykly

2
"стек - це пам'ять, відведена як місце для подряпин". Класно. Але де це насправді "відкладено" з точки зору структури пам'яті Java ?? Чи це пам’ять Heap / Пам'ять, що не має купи / Інше (Структура пам'яті Java згідно betsol.com/2017/06/… )
Jatin Shashoo

4
@JatinShashoo Java, як інтерпретатор байт-коду, додає ще один рівень віртуалізації, тому те, про що ви посилалися, є лише точкою зору програми Java. З точки зору операційної системи, все це лише купа, де процес виконання Java виділяє частину свого простору як "негромову" пам'ять для обробленого байт-коду. Решта цієї купи на ОС використовується як купа рівня програми, де зберігаються дані об’єкта.
kbec

2349

Стек:

  • Зберігається в комп'ютерній оперативній пам’яті так само, як у купі.
  • Змінні, створені у стеці, вийдуть за межі сфери та автоматично будуть розміщені.
  • Набагато швидше виділити порівняно зі змінними на купі.
  • Реалізовано з фактичною структурою даних стека.
  • Зберігає локальні дані, зворотні адреси, використовувані для передачі параметрів.
  • Може мати переповнення стека, коли використовується занадто велика кількість стека (в основному від нескінченної або занадто глибокої рекурсії, дуже великих розмірів).
  • Дані, створені на стеку, можна використовувати без покажчиків.
  • Ви б використовували стек, якщо точно знаєте, скільки даних потрібно виділити до часу компіляції, і він не надто великий.
  • Зазвичай має максимальний розмір, що вже визначається при запуску програми.

Купи:

  • Зберігається в комп'ютерній оперативній пам’яті так само, як стек.
  • У C ++ змінні на купі необхідно знищувати вручну і ніколи не виходити за межі області. Дані звільнено з delete, delete[]абоfree .
  • Повільніше виділяти порівняно зі змінними на стеку.
  • Використовується на вимогу для виділення блоку даних для використання програмою.
  • Може мати роздробленість, коли виділень та угод багато.
  • У C ++ або C дані, створені на купі, будуть вказуватися вказівниками та розподілятися відповідно newабо mallocвідповідно.
  • Може мати збої в розподілі, якщо вимагається виділити занадто великий буфер.
  • Ви б використовували купу, якщо не знаєте точно, скільки даних вам знадобиться під час виконання, або якщо вам потрібно виділити багато даних.
  • Відповідає за витоки пам'яті.

Приклад:

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;

31
Вказівник pBuffer і значення b розташовані на стеці і, здебільшого, ймовірно виділяються на вході до функції. Залежно від компілятора, буфер може бути виділений і на вході функції.
Енді

36
Поширена помилкова думка, що Cмова, визначена C99мовним стандартом (доступна на сайті open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf ), вимагає "стека". Насправді, слово 'стек' навіть у стандарті не з’являється. Це відповідає висловлюванням wrt / to C's' stack, як правило, правда, але мова жодним чином не вимагає. Докладнішу інформацію див. У розділі knosof.co.uk/cbook/cbook.html , зокрема про те, як Cреалізується на архітектурах непарних кульок, таких як en.wikipedia.org/wiki/Burroughs_large_systems
johne

55
@Brian Ви повинні пояснити, чому буфер [] та вказівник pBuffer створюються на стеці та чому дані pBuffer створюються у купі. Я думаю, що деякі ppl можуть бути заплутані вашою відповіддю, оскільки вони можуть подумати, що програма спеціально вказує на те, щоб пам'ять була розподілена на стек проти купи, але це не так. Це тому, що буфер - тип значення, тоді як pBuffer - тип посилання?
Howiecamp

9
@ Усунення: жоден вказівник не містить адреси, і він може вказувати на щось на купі або на стеку однаково. нові, malloc та деякі інші функції, схожі на malloc, виділяють у купу і повертають адресу пам'яті, яка була виділена. Чому б ви хотіли виділяти на купу? Так що ваша пам’ять не вийде за межі сфери та звільниться, поки ви цього не захочете.
Брайан Р. Бонді

35
"Відповідальний за витоки пам'яті" - Купи не відповідають за витоки пам'яті! Ліниві / незабутні / колишні кодери / кодери, які не дають лайно!
Лаз

1370

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

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

    Складіть, як стопку паперів

    Простота стека полягає в тому, що вам не потрібно підтримувати таблицю, що містить запис кожного розділу виділеної пам'яті; Єдина необхідна вам інформація про стан - це єдиний вказівник на кінець стека. Щоб виділити та виділити, ви просто збільшуєте та зменшуєте цей єдиний вказівник. Примітка: стек іноді може бути реалізований для початку у верхній частині частини пам'яті та розширення вниз, а не зростання вгору.

  • У купі немає особливого порядку, як розміщуються предмети. Ви можете зайти та вилучити елементи в будь-якому порядку, оскільки немає чіткого "верхнього" елемента.

    Купа, як купа солодких солод

    Розподіл купи вимагає збереження повного запису на те, що виділено пам'ять, а що ні, а також деяке накладне обслуговування, щоб зменшити фрагментацію, знайти суміжні сегменти пам'яті досить великі, щоб відповідати потрібному розміру тощо. Пам'ять може бути розміщена в будь-який час, залишаючи вільний простір. Іноді розподільник пам'яті виконуватиме завдання технічного обслуговування, такі як дефрагментація пам'яті шляхом переміщення виділеної пам’яті навколо або збирання сміття - ідентифікуючи під час виконання, коли пам’ять більше не знаходиться в обсягах, і розбирає її.

Ці зображення повинні зробити досить хорошу роботу з опису двох способів розподілу та звільнення пам’яті в стеку та купі. Ам!

  • Наскільки вони контролюються ОС або мовою виконання?

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

    Купа - загальний термін, що використовується для будь-якої пам'яті, яка розподіляється динамічно та випадковим чином; тобто не в порядку. Пам'ять, як правило, виділяється ОС, при цьому додаток викликає функції API для цього розподілу. Для управління динамічно розподіленою пам’яттю потрібна велика кількість накладних витрат, яка, як правило, обробляється кодом виконання використовуваної мови програмування або середовища.

  • Яка сфера їх застосування?

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

  • Від чого залежить розмір кожного з них?

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

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

  • Що робить одного швидше?

    Стек швидше, тому що вся вільна пам'ять завжди суміжна. Не слід підтримувати список усіх сегментів вільної пам'яті, лише один покажчик на поточну вершину стека. Для цього компілятори зазвичай зберігають цей покажчик у спеціальному, швидкому реєстрі . Більше того, наступні операції на стеку зазвичай зосереджені в дуже близьких областях пам'яті, що на дуже низькому рівні добре для оптимізації кешованими процесорами.


20
Девід. Я не погоджуюся, що це хороший образ або що «штовхаючий стек» - це хороший термін для ілюстрації концепції. Коли ви щось додаєте в стек, інші вмісти стеку не висуваються вниз, вони залишаються там, де вони є.
thomasrutter

8
Ця відповідь включає велику помилку. Статичні змінні в стеці не виділяються. Дивіться мою відповідь [посилання] stackoverflow.com/a/13326916/1763801 для уточнення. ви порівнюєте "автоматичні" змінні зі "статичними" змінними, але вони зовсім не однакові
davec

13
Зокрема, ви кажете, що "статично розподілені локальні змінні" виділяються на стеку. Насправді вони розподілені в сегменті даних. На стек виділяються лише автоматично розподілені змінні (що включає більшість, але не всі локальні змінні, а також речі, такі як параметри функції, передані за значенням, а не за посиланням).
davec

9
Я щойно зрозумів, що ти маєш рацію - у С статичне виділення - це його окрема річ, а не термін для всього, що не динамічно . Я відредагував свою відповідь, дякую.
thomasrutter

5
Це не тільки C. Java, Pascal, Python та багато інших. Всі вони мають уявлення про статичне порівняно з автоматичним та динамічним розподілом. Казати "статичне виділення" означає те саме, що скрізь скрізь. Ні в якій мові статичне виділення не означає "не динамічне". Ви хочете, щоб термін "автоматичне" виділення для того, що ви описуєте (тобто речей у стеку).
davec

727

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

Відповідь на ваше запитання залежить від конкретного впровадження та може відрізнятись від компіляторів та архітектур процесора. Однак ось спрощене пояснення.

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

Купа

  • Купа містить пов'язаний список використаних та безкоштовних блоків. Нові асигнування на купі (на newабо malloc) задовольняються створенням відповідного блоку з одного з вільних блоків. Для цього потрібно оновити список блоків на купі. Ця мета-інформація про блоки на купі також зберігається на купі часто на невеликій площі перед кожним блоком.
  • У міру зростання купи часто виділяються нові блоки від нижчих адрес до більш високих адрес. Таким чином, ви можете думати про купу як купу блоків пам'яті, що збільшується в міру розподілу пам'яті. Якщо купа занадто мала для виділення, розмір часто може бути збільшений, придбавши більше пам'яті від базової операційної системи.
  • Виділення та розміщення багатьох невеликих блоків може залишити купу в стані, коли між використовуваними блоками є багато маленьких вільних блоків. Запит на виділення великого блоку може виявитися невдалим, оскільки жоден з вільних блоків не є достатньо великим, щоб задовольнити запит на розподіл, навіть якщо комбінований розмір вільних блоків може бути досить великим. Це називається купиною фрагментації .
  • Коли використаний блок, який примикає до вільного блоку, розміщений, новий вільний блок може бути об'єднаний з сусіднім вільним блоком, щоб створити більший вільний блок, ефективно зменшуючи фрагментацію купи.

Купа

Стек

  • Стек часто працює в тісному тандемі зі спеціальним регістром на процесорі з назвою вказівник стека . Спочатку вказівник стека вказує на верхню частину стека (найвища адреса стека).
  • ЦП має спеціальні інструкції щодо переміщення значень на стек та виведення їх назад із стеку. Кожен поштовх зберігає значення в поточному місці вказівника стека і зменшує покажчик стека. А поп витягує значення , на яке вказує покажчик стека , а потім збільшує покажчик стека (не слід плутати з тим , що при додаванні значення в стек зменшує покажчик стека і видалення значення збільшується його. Пам'ятайте , що стек росте знизу). Значення, що зберігаються та витягуються - це значення регістрів процесора.
  • Коли функція викликається процесором, використовуються спеціальні інструкції, які висувають поточний покажчик інструкцій , тобто адресу коду, що виконується на стеку. Потім процесор переходить до функції, встановлюючи вказівник інструкції на адресу функції, що викликається. Пізніше, коли функція повертається, старий вказівник інструкції вискакує зі стека і виконання відновлюється за кодом відразу після виклику функції.
  • Коли функція введена, покажчик стека зменшується, щоб виділити більше місця на стеку для локальних (автоматичних) змінних. Якщо функція має одну локальну 32-бітну змінну, чотири байти відкладаються на стеку. Коли функція повертається, покажчик стека переміщується назад, щоб звільнити виділену область.
  • Якщо функція має параметри, вони висуваються на стек перед викликом функції. Код у цій функції може потім переміщатися по стеку з поточного вказівника стека, щоб знайти ці значення.
  • Виклики функції введення функціонують як принадність. Кожен новий виклик буде виділяти параметри функції, зворотну адресу та простір для локальних змінних, і ці записи активації можуть бути складені для вкладених викликів і розмотаються правильним чином, коли функції повернуться.
  • Оскільки стек є обмеженим блоком пам'яті, ви можете викликати переповнення стека , викликаючи занадто багато вкладених функцій та / або виділяючи занадто багато місця для локальних змінних. Часто область пам'яті, що використовується для стека, налаштована таким чином, що запис нижче нижньої (найнижчої адреси) стека викликає пастку або виняток у ЦП. Ця виняткова умова може бути спіймана під час виконання та перетворена на якусь виняток переповнення стека.

Стек

Чи може бути виділена функція на купі замість стека?

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

Як керувати купою, насправді залежить від середовища виконання. C використовує mallocі C ++ використовує new, але багато інших мов мають сміття.

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


35
@Martin - Дуже хороша відповідь / пояснення, ніж більш абстрактна прийнята відповідь. Зразкова програма складання, що показує покажчики / регістри стеків, що використовуються для викликів функцій, була б більш показовою.
Bikal Lem

3
Кожен тип посилання є складом типів значень (int, string тощо). Як говориться, типи значень зберігаються в стеку, ніж як це працює, коли вони є частиною еталонного типу.
Nps

15
На мою думку, ця відповідь була найкращою, тому що вона допомогла мені зрозуміти, що таке насправді зворотний вислів і як воно пов’язане з цією "адресою повернення", яку я щоразу зустрічаю, що означає натискати функцію на стек, і чому функції висуваються на стеки. Чудова відповідь!
Алекс

3
Це найкраще , на мій погляд, саме за згадку про те , що купа / стек є дуже конкретної реалізації. Інші відповіді передбачають багато речей про мову та навколишнє середовище / ОС. +1
Qix - МОНІКА ПОМІСТИЛА

2
Що ви маєте на увазі "Код у функції може потім переміщатися по стеку з поточного вказівника стека, щоб знайти ці значення". ? Чи можете ви детальніше зупинитися на цьому?
Корай Тугай

404

У наступному C # коді

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

Ось як керується пам'яттю

Зображення змінних у стеку

Local Variablesщо потрібно лише тривати, доки виклик функції зберігається в стеці. Купа використовується для змінних, термін експлуатації яких насправді не знаємо, але ми очікуємо, що вони триватимуть деякий час. У більшості мов важливо, щоб ми знали під час компіляції, наскільки велика змінна, якщо ми хочемо зберегти її у стеку.

Об'єкти (які різняться за розміром, коли ми їх оновлюємо) йдуть на купу, оскільки ми не знаємо на час створення, скільки часу вони триватимуть. У багатьох мовах купа сміття збирається для пошуку об’єктів (наприклад, об'єкта cls1), на які більше немає посилань.

У Java більшість об'єктів прямують у купу. У таких мовах, як C / C ++, структури та класи часто можуть залишатися на стеку, коли ви не маєте справу з покажчиками.

Більше інформації можна знайти тут:

Різниця між розподілом стека та накопичувальної пам'яті «timmurphy.org

і ось:

Створення об'єктів у стеці та купі

Ця стаття є джерелом малюнка вище: Шість важливих .NET понять: стек, купа, типи значень, типи посилань, бокс та розпакування - CodeProject

але пам’ятайте, що він може містити деякі неточності.


15
Це неправильно. i і cls не є "статичними" змінними. їх називають "локальними" або "автоматичними" змінними. Це дуже важлива відмінність. Див [посилання] stackoverflow.com/a/13326916/1763801 роз'яснень
davec

9
Я не сказав, що вони були статичними змінними . Я сказав, що int і cls1 - статичні елементи . Їх пам'ять статично розподілена, і тому вони йдуть на стек. Це на відміну від об'єкта, який вимагає динамічного розподілу пам’яті, який, отже, йде в купу.
Сніговий удар

12
Я цитую "Статичні елементи ... йди на стек". Це просто неправильно. Статичні елементи йдуть у сегменті даних, автоматичні елементи - у стеку.
davec

14
Також той, хто написав цю статтю про кодекс, не знає, про що йде мова. Наприклад, він каже, що "примітивним потрібна пам'ять статичного типу", що абсолютно не відповідає дійсності. Ніщо не заважає динамічно розподіляти примітиви в купі, просто напишіть щось на зразок "int array [] = new int [num]" та voila, примітиви, що динамічно розподіляються у .NET. Це лише одна з кількох неточностей.
davec

8
Я відредагував ваше повідомлення, тому що ви допустили серйозні технічні помилки щодо того, що йде в стек і купу.
Том Лейс

209

Стек Коли ви викликаєте функцію, аргументи цієї функції плюс деякі інші накладні передаються в стек. Деякі відомості (наприклад, куди повернутись) також зберігаються там. Коли ви оголошуєте змінну всередині своєї функції, ця змінна також виділяється в стеці.

Розділити стек досить просто, тому що ви завжди розміщуєте їх у зворотному порядку, в якому ви виділяєте. Елементи стека додаються під час введення функцій, відповідні дані видаляються під час виходу з них. Це означає, що ви, як правило, залишаєтесь у невеликій області стека, якщо ви не викликаєте безліч функцій, які викликають безліч інших функцій (або створюють рекурсивне рішення).

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

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

Впровадження Реалізація як стека, так і купи зазвичай залежить від часу виконання / ОС. Часто ігри та інші програми, що мають критичну ефективність, створюють власні рішення для пам’яті, які захоплюють велику частину пам’яті з купи, а потім передають її всередину, щоб не покладатися на ОС для пам’яті.

Це практично лише в тому випадку, якщо використання вашої пам’яті сильно відрізняється від норми - тобто для ігор, де ви завантажуєте рівень за одну величезну операцію і можете відкинути всю партію в іншій величезній операції.

Фізичне розташування в пам'яті Це менш актуально, ніж ви думаєте, через технологію під назвою Virtual Memory, яка змушує вашу програму думати, що у вас є доступ до певної адреси, де фізичні дані десь ще (навіть на жорсткому диску!). Адреси, які ви отримуєте для стека, набирають порядок, оскільки дерево ваших викликів стає все глибшим. Адреси для купи непередбачувані (тобто специфічні для імліментації) і, відверто кажучи, не важливі.


16
Рекомендація уникати використання купи досить сильна. Сучасні системи мають хороших менеджерів купи, а сучасні динамічні мови широко використовують цю групу (без програміста це справді не турбується). Я б сказав, використовуйте купу, але з ручним розподільником, не забудьте безкоштовно!
Грег Хьюгілл

2
Якщо ви можете використовувати стек або купу, використовуйте стек. Якщо ви не можете використовувати стек, насправді вибору немає. Я дуже багато використовую, і звичайно використовую std :: vector або подібні хіти до купи. Для новачків ви уникаєте купи, тому що стек просто такий простий !!
Том Лейс

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

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

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

194

Для уточнення, у цій відповіді є неправильна інформація ( томи виправили свою відповідь після коментарів, круто :)). Інші відповіді просто уникають пояснення, що означає статичний розподіл. Тому я поясню три основні форми розподілу та те, як вони зазвичай відносяться до сегменту купи, стеку та даних нижче. Я також покажу кілька прикладів як на C / C ++, так і на Python, щоб допомогти людям зрозуміти.

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

Однак, як правило, краще розглядати " область " та " термін експлуатації ", а не "стек" і "купу".

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

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

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

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

// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be "leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

Особливо кричущим прикладом того, чому важливо розрізняти час життя та область застосування, є те, що змінна може мати локальну область, але статичну тривалість життя - наприклад, "someLocalStaticVariable" у зразку коду вище. Такі змінні можуть зробити наші загальні, але неофіційні звички називати дуже заплутаними. Наприклад, коли ми кажемо " локальний ", ми зазвичай маємо на увазі " локально розподілену автоматично розподілену змінну ", а коли ми говоримо глобальну, ми зазвичай маємо на увазі " глобально оцінену статично розподілену змінну ". На жаль, коли мова йде про такі речі, як " файл, що має статично розподілені змінні ", багато людей просто кажуть ... " так ??? ".

Деякі варіанти синтаксису в C / C ++ посилюють цю проблему - наприклад, багато людей вважають, що глобальні змінні не є "статичними" через синтаксис, показаний нижче.

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

Зауважте, що розміщення ключового слова "статичний" у вищезазначеній декларації заважає var2 мати глобальну сферу застосування. Тим не менш, глобальний var1 має статичне розподіл. Це не інтуїтивно! З цієї причини я намагаюся ніколи не використовувати слово "статичний" при описі області, а натомість кажу щось на кшталт "файл" або "файл обмежений". Однак багато людей використовують фразу "статичний" або "статичний діапазон" для опису змінної, доступ до якої можна отримати лише з одного кодового файлу. У контексті життєвого циклу "статична" завжди означає, що змінна виділяється при запуску програми та розміщується при виході програми.

Деякі люди вважають ці поняття специфічними для C / C ++. Вони не. Наприклад, зразок Python, наведений нижче, ілюструє всі три типи розподілу (можливі деякі тонкі відмінності в інтерпретованих мовах, які я тут не потрапляю).

from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __name__ == "__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones

Я б назвав статичну змінну, оголошену у функції, яка має лише локальну доступність , але, як правило, не використовує з нею термін "область". Крім того, можливо, варто відзначити, що один аспект стека / купи, з яким мови мають по суті нульову гнучкість: мова, яка зберігає контекст виконання на стеку, не може використовувати цей самий стек для зберігання речей, для яких потрібно пережити контексти, в яких вони створені. . Деякі мови, як-от, PostScriptмають кілька стеків, але мають "купу", яка більше схожа на стек.
supercat

@supercat Це все має сенс. Я визначив область застосування як "які частини коду можуть отримати доступ до змінної" (і вважаю, що це найбільш стандартне визначення), тому я думаю, що ми згодні :)
davec

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

@supercat Ось чому я використовую слово "життя", саме так я називаю те, що ви називаєте часовим діапазоном. Це зменшує необхідність перевантажувати слово "сфера" настільки великими значеннями. Наскільки я можу сказати, не існує загальної консенсусу щодо точних визначень, хоча навіть серед канонічних джерел. Моя термінологія виходить частково з K&R та частково з переважного використання на першому відділі CS, в якому я навчався / викладав. Завжди добре чути інший поінформований погляд.
davec

1
ви напевно жартуєте. чи можна дійсно визначити статичну змінну всередині функції?
Zaeem Sattar

168

Інші відповіли на широкі штрихи досить добре, тому я накину кілька деталей.

  1. Стек і купа не повинні бути єдиними. Поширена ситуація, коли у вас є більше одного стека, якщо у вас є більше одного потоку в процесі. У цьому випадку кожна нитка має свій стек. Ви також можете мати більше однієї купи, наприклад, деякі конфігурації DLL можуть призвести до виділення різних DLL-файлів з різних груп, тому загалом погана ідея звільнення пам'яті, виділеної іншою бібліотекою.

  2. У C ви можете отримати вигоду від розподілу довжини змінної за рахунок використання аллока , який виділяє на стек, на відміну від аллока, який виділяє на купу. Ця пам'ять не переживе ваше повернення, але корисно для буфера подряпин.

  3. Зробити величезний тимчасовий буфер для Windows, яким ви багато не користуєтесь, це не безкоштовно. Це пояснюється тим, що компілятор генерує цикл зондування стека, який викликається щоразу, коли ваша функція вводиться, щоб переконатися, що стек існує (адже Windows використовує одну сторожу сторінки в кінці вашого стека, щоб визначити, коли йому потрібно наростити стек. Якщо ви отримаєте доступ до пам’яті більше однієї сторінки з кінця стека, ви будете вибиті з ладу). Приклад:

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}

Re "на відміну від alloc": Ви маєте на увазі "на відміну від malloc"?
Пітер Мортенсен

Наскільки портативний alloca?
Пітер Мортенсен

@PeterMortensen це не POSIX, портативність не гарантована.
Дон Нойфельд

135

Інші безпосередньо відповіли на ваше запитання, але, намагаючись зрозуміти стек і купу, я думаю, що корисно розглянути компонування пам’яті традиційного процесу UNIX (без потоків та mmap()алокаторів на основі). Управління пам'яттю Глосарій веб - сторінка має діаграму цієї схеми пам'яті.

Стек і купа традиційно розташовані на протилежних кінцях віртуального адресного простору процесу. Стек автоматично збільшується при доступі до розміру, встановленого ядром (який можна регулювати за допомогою setrlimit(RLIMIT_STACK, ...)). Купа зростає, коли розподільник пам'яті викликає brk()або sbrk()виклик системи, відображаючи більше сторінок фізичної пам'яті у віртуальний адресний простір процесу.

У системах без віртуальної пам’яті, таких як деякі вбудовані системи, часто застосовується однаковий базовий макет, за винятком того, що стек і купа мають фіксований розмір. Однак в інших вбудованих системах (таких, як на основі мікроконтролерів Microchip PIC) програмний стек - це окремий блок пам'яті, який не можна адресувати вказівками щодо руху даних, і його можна змінювати чи читати лише опосередковано за допомогою інструкцій програмного потоку (дзвінок, повернення тощо). Інші архітектури, такі як процесори Intel Itanium, мають кілька стеків . У цьому сенсі стек є елементом архітектури процесора.


117

Що таке стек?

Стек - це купа предметів, як правило, чітко розташованих.

Введіть тут опис зображення

Стеки в обчислювальній архітектурі - це області пам'яті, де дані додаються або видаляються в режимі "останній-перший-вихід".
У багатопотоковому додатку кожен потік матиме свій стек.

Що таке купа?

Купа - це неохайна колекція речей, зібраних випадково.

Введіть тут опис зображення

У архітектурах обчислень купу - це область динамічно розподіленої пам'яті, яка управляється автоматично операційною системою або бібліотекою менеджера пам'яті.
Пам'ять у купі виділяється, розміщується та змінюється регулярно під час виконання програми, і це може призвести до проблеми, яка називається фрагментацією.
Фрагментація виникає, коли об'єкти пам’яті виділяються з невеликими пробілами між ними, які є занадто маленькими, щоб вмістити додаткові об’єкти пам’яті.
Чистий результат - це відсоток простору купи, який не можна використовувати для подальшого розподілу пам'яті.

Обидва разом

У багатопотоковому додатку кожен потік матиме свій стек. Але всі різні нитки поділять купу.
Оскільки різні потоки ділять купу в багатопотоковому додатку, це також означає, що між потоками повинна бути деяка координація, щоб вони не намагалися отримати доступ та маніпулювати тим самим фрагментом пам'яті в купі на одночасно.

Що швидше - стек чи купа? І чому?

Стек набагато швидше, ніж купа.
Це пояснюється тим, як пам'ять виділяється на стек.
Розміщення пам'яті в стеці так само просто, як переміщення покажчика стека вгору.

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

Модель пам'яті Java

Введіть тут опис зображення

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


115

Стек - це частина пам'яті, якою можна керувати за допомогою декількох ключових інструкцій мови складання, таких як 'pop' (видалити та повернути значення зі стека) та 'push' (натиснути значення на стек), а також зателефонувати ( викликати підпрограму - це підштовхує адресу для повернення в стек) і повертається (повернення з підпрограми - це вискакує адресу зі стека і переходить до неї). Це область пам'яті нижче регістра вказівника стека, яку можна встановити за потребою. Стек також використовується для передачі аргументів в підпрограми, а також для збереження значень в регістрах перед викликом підпрограм.

Купа - це частина пам'яті, яка надається програмі операційною системою, як правило, через системний виклик, як malloc. У сучасних ОС ця пам'ять - це набір сторінок, до яких має доступ лише процес виклику.

Розмір стека визначається під час виконання і, як правило, не збільшується після запуску програми. У програмі C стек повинен бути достатньо великим, щоб вмістити кожну змінну, оголошену в межах кожної функції. Купа динамічно зростатиме за потребою, але ОС в кінцевому підсумку здійснює виклик (вона часто збільшуватиме купу більше, ніж значення, яке вимагає malloc, так що принаймні деяким майбутнім малокам не потрібно буде повертатися до ядра, щоб отримати більше пам’яті. Ця поведінка часто налаштовується)

Оскільки ви виділили стек перед запуском програми, вам ніколи не потрібно робити malloc перед тим, як використовувати стек, тому це невелика перевага там. На практиці дуже важко передбачити, що буде швидко і що буде повільно в сучасних операційних системах, які мають підсистеми віртуальної пам’яті, адже те, як сторінки реалізуються та де вони зберігаються, - це деталь реалізації.


2
Тут також слід зазначити, що Intel значно оптимізує доступ до стеку, особливо такі речі, як передбачення, куди ви повернетесь із функції.
Том Лейс

113

Я думаю, що багато інших людей дали вам здебільшого правильні відповіді з цього приводу.

Однак одна з деталей, яка була пропущена, - це те, що "купу" насправді, мабуть, слід назвати "безкоштовним магазином". Причиною цього розрізнення є те, що оригінальний безкоштовний магазин був реалізований із структурою даних, відомою як "біноміальна купа". З цієї причини виділення з ранньої реалізації malloc () / free () було виділено з купи. Однак у цей сучасний час більшість безкоштовних магазинів реалізовані з дуже досконалою структурою даних, яка не є двочленними купами.


8
Ще одна відповідь - більшість відповідей (злегка) передбачає, що використання "стека" потрібна Cмові. Це поширене помилкове уявлення, хоча це (на сьогоднішній день) парадигма домінування щодо реалізації C99 6.2.4 automatic storage duration objects(змінних). Насправді слово "стек" навіть не з’являється у C99мовному стандарті: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
johne

[@Heath] Я малий коментар до вашої відповіді. Погляньте на прийняту відповідь на це питання . Це говорить про те, що вільний магазин, швидше за все , такий самий, як і купа , хоча це не обов'язково.
ОмарОтман

91

Ви можете зробити кілька цікавих речей зі стеком. Наприклад, у вас є такі функції, як аллока (припускаючи, що ви можете пройти рясні попередження щодо її використання), що є формою malloc, яка спеціально використовує стек, а не купу, для пам'яті.

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


Наскільки портативний alloca? Наприклад, чи працює це в Windows? Це лише для Unix-подібних операційних систем?
Пітер Мортенсен

89

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

Купа - область пам’яті, що динамічно розподіляє пам’ять, зроблені з (явні «нові» або «розподілити» дзвінки). Це особлива структура даних, яка може відслідковувати блоки пам'яті різного розміру та їх статус розподілу.

У «класичних» системах оперативна пам’ять була розміщена таким чином, що покажчик стека починався внизу пам’яті, вказівник купи запускався вгорі, і вони зростали назустріч один одному. Якщо вони перекриваються, у вас немає оперативної пам’яті. Однак це не працює з сучасними багатопотоковими ОС. Кожна нитка повинна мати власний стек, і вони можуть створюватися динамічно.


[@ TED] Чому ви сказали "іноді параметри висуваються на стек"? Я знаю, що вони завжди є. Не могли б ви докладніше розробити детальніше?
ОмарОтман

1
@OmarOthman - я кажу, що тому, що повністю залежить від автора вашого компілятора / інтерпретатора, що відбувається, коли викликається підпрограма. Класична поведінка Fortran - взагалі не використовувати стек. Деякі мови підтримують екзотичні речі, як-от "перейми за назвою", що фактично є текстовою заміною.
ТЕД

83

Від WikiAnwser.

Стек

Коли функція або метод викликає іншу функцію, яка по черзі викликає іншу функцію тощо, виконання всіх цих функцій залишається призупиненим, поки сама остання функція не поверне своє значення.

Цей ланцюг призупинених функціональних викликів є стеком, оскільки елементи стеку (виклики функцій) залежать один від одного.

Стек важливо враховувати при обробці винятків та виконанні потоків.

Купи

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


"Мені більше подобається прийнята відповідь, оскільки це ще нижчий рівень". Це погана річ, не гарна річ.
Гонки легкості на орбіті

54

Стек

  • Дуже швидкий доступ
  • Не потрібно чітко розподіляти змінні
  • Простір ефективно керується процесором, пам'ять не стане фрагментованою
  • Тільки локальні змінні
  • Обмеження розміру стека (залежно від ОС)
  • Змінення змінних неможливо змінити

Купи

  • До змінних можна отримати доступ у всьому світі
  • Немає обмеження щодо розміру пам'яті
  • (Відносно) повільніший доступ
  • Немає гарантованого ефективного використання простору, пам'ять може з часом фрагментуватися, коли блоки пам'яті розподіляються, а потім звільняються
  • Ви повинні керувати пам'яттю (ви відповідаєте за розподіл та звільнення змінних)
  • Змінення змінних можна змінити за допомогою realloc ()

50

Добре, просто і коротко кажучи, вони означають впорядкований і не замовлений ...!

Стек : У елементах стеку речі стають один на одного, це означає, що швидше та ефективніше буде оброблятися! ...

Отже, завжди є індекс, який вказує на конкретний елемент, також обробка буде швидшою, а також між елементами є зв'язок! ...

Купи : Жодного порядку, обробка не буде повільнішою, а значення змішуються разом без конкретного порядку чи індексу ... між ними є випадкові випадки, а між ними немає зв'язку, тому час виконання та використання може бути різним ...

Я також створюю зображення нижче, щоб показати, як вони можуть виглядати:

введіть тут опис зображення


49

Коротко

Стек використовується для статичного розподілу пам’яті та купа динамічного розподілу пам’яті, що зберігаються в оперативній пам’яті комп'ютера.


Детально

Стек

Стек - це структура даних "LIFO" (останнє, перше), яка керується та оптимізується процесором досить близько. Кожен раз, коли функція оголошує нову змінну, її "натискають" на стек. Потім кожного разу, коли функція виходить, всі змінні, висунуті на стек цією функцією, звільняються (тобто видаляються). Після звільнення змінної стека ця область пам'яті стає доступною для інших змінних стека.

Перевага використання стека для зберігання змінних полягає в тому, що пам'ять керована для вас. Вам не потрібно виділяти пам'ять вручну або звільняти її, як тільки вона вам більше не потрібна. Більше того, оскільки процесор так ефективно організовує стек-пам'ять, читання та запис змінних в стек дуже швидко.

Більше можна знайти тут .


Куча

Купа - це область пам’яті вашого комп'ютера, яка не управляється автоматично для вас і не так щільно управляється процесором. Це більш вільно плаваюча область пам'яті (і є більшою). Щоб розподілити пам'ять на купі, потрібно використовувати malloc () або calloc (), які є вбудованими функціями C. Виділивши пам'ять на купі, ви несете відповідальність за використання free () для розміщення цієї пам'яті, коли вона вам більше не потрібна.

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

На відміну від стека, купа не має обмежень щодо розміру змінних розмірів (крім очевидних фізичних обмежень вашого комп'ютера). Пам’ять купи трохи повільніше читати і записувати в неї, тому що для доступу до пам'яті в купі потрібно використовувати вказівники. Про покажчики ми поговоримо найближчим часом.

На відміну від стека, змінні, створені на купі, доступні будь-якій функції в будь-якій точці вашої програми. Змінні купи по суті є глобальними.

Більше можна знайти тут .


Змінні, виділені в стеці, зберігаються безпосередньо в пам'яті, і доступ до цієї пам'яті відбувається дуже швидко, і з її розподілом відбувається, коли програма компілюється. Коли функція або метод викликає іншу функцію, яка по черзі викликає іншу функцію тощо, виконання всіх цих функцій залишається призупиненим, поки сама остання функція не поверне своє значення. Стек завжди зарезервований у порядку LIFO, останній зарезервований блок - це завжди наступний блок, який повинен бути звільнений. Це робить насправді простим відстеження стека, звільнення блоку зі стека - це не що інше, як коригування одного вказівника.

Змінні, що виділяються в купі, виділяють пам'ять під час виконання, а доступ до цієї пам'яті дещо повільніше, але розмір купи обмежений лише розміром віртуальної пам'яті. Елементи купи не залежать один від одного і завжди можна отримати доступ довільно в будь-який час. Ви можете виділити блок у будь-який час та звільнити його в будь-який час. Це робить набагато складніше відслідковувати, які частини купи виділяються чи вільні в будь-який момент часу.

Введіть тут опис зображення

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

У багатопотокової ситуації кожна нитка матиме власний повністю незалежний стек, але вони поділять купу. Стек є потоковим, а купа - специфічною для програми. Стек важливо враховувати при обробці винятків та виконанні потоків.

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

Введіть тут опис зображення

Якщо під час виконання програми потрібна більша купа, вона може виділяти пам'ять з вільної пам'яті, а якщо стеку потрібна пам'ять, вона може виділяти пам'ять із вільної пам'яті, виділеної для програми.

Навіть, детальніше тут і тут .


Тепер приходьте до відповідей вашого питання .

Наскільки вони контролюються ОС або мовою виконання?

ОС створює стек для кожного потоку на рівні системи під час створення потоку. Зазвичай ОС викликається мовою виконання, щоб виділити купу для програми.

Більше можна знайти тут .

Яка сфера їх застосування?

Вже дано вгорі.

"Ви можете використовувати стек, якщо точно знаєте, скільки даних потрібно виділити перед тим, як час компіляції, і він не надто великий. Ви можете використовувати купу, якщо ви точно не знаєте, скільки даних вам знадобиться під час виконання або якщо вам потрібно виділити багато даних ".

Більше можна знайти тут .

Від чого залежить розмір кожного з них?

Розмір стека встановлюється ОС при створенні потоку. Розмір купи встановлюється при запуску програми, але він може зростати в міру необхідності місця (алокатор вимагає більше оперативної пам'яті).

Що робить одного швидше?

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

Крім того, стек проти купи - це не лише врахування продуктивності; це також багато розповідає про очікуваний термін експлуатації об'єктів.

Деталі можна знайти тут .


36

У 1980-х роках UNIX поширювався на зразок зайчиків з великими компаніями, котивши свої власні. У Exxon було десять марок, загублених в історії. Як складалася пам'ять, на розсуд багатьох виконавців.

Типова програма C була викладена в пам'яті з можливістю збільшення, змінивши значення brk (). Як правило, HEAP був трохи нижче цього значення brk, а збільшення brk збільшувало кількість доступної купи.

Одиничний STACK був типово областю нижче HEAP, що являло собою тракт пам'яті, що не містить нічого цінного до початку наступного фіксованого блоку пам'яті. Наступний блок часто був CODE, який можна було перезаписати даними стека в одному з відомих хаків своєї епохи.

Одним типовим блоком пам’яті був BSS (блок нульових значень), який випадково не був занурений в пропозиції одного виробника. Іншим був DATA, що містить ініціалізовані значення, включаючи рядки та числа. Третьою була CODE, що містить CRT (C runtime), основні, функції та бібліотеки.

Поява віртуальної пам’яті в UNIX змінює багато обмежень. Немає жодної об'єктивної причини, чому ці блоки повинні бути суміжними, або закріпленими за розміром, або замовляти певний спосіб зараз. Звичайно, раніше UNIX був Multics, який не зазнав цих обмежень. Ось схематично показано один із макетів пам'яті тієї епохи.

Типовий макет пам'яті програм UNIX C для 1980-х років



26

Пару центів: Я думаю, буде добре намалювати графічну пам'ять і простіше:

Це моє бачення побудови процесорної пам’яті зі спрощенням для легшого розуміння того, що відбувається


Стрілки - показують, де ростуть стек і купа, розмір стека процесів має обмеження, визначене в ОС, обмеження розміру стека потоку за параметрами в API створення потоку зазвичай. Купа, як правило, обмежує максимальний обсяг віртуальної пам’яті, наприклад, для 32-бітових 2-4 ГБ.

Настільки простий спосіб: купа процесів є загальною для процесу та всіх потоків всередині, використовуючи для розподілу пам'яті в загальному випадку з чимось на зразок malloc () .

Стек - це швидка пам'ять для зберігання загальних покажчиків повернення та змінних функцій регістру, оброблених як параметри виклику функції, локальних змінних функцій.


23

Оскільки деякі відповіді пішли, я збираюся внести свій внесок у кліщ.

Дивно, але ніхто не згадав, що багатократні (тобто не пов'язані з кількістю запущених потоків на рівні ОС) стеки викликів можна знайти не тільки в екзотичних мовах (PostScript) або платформах (Intel Itanium), а й у волокнах , зелених нитках і деякі реалізації співпрограми .

Волокна, зелені нитки та сорочки багато в чому схожі, що призводить до великої плутанини. Різниця між волокнами та зеленими нитками полягає в тому, що перші використовують кооперативну багатозадачність, а другі можуть містити кооперативну чи попереджувальну (або навіть обидві). Про відмінність між волокнами та корутинами дивіться тут .

У будь-якому випадку метою обох волокон, зелених ниток і взаємоконтролю є виконання декількох функцій, які виконуються одночасно, але не паралельно (див. Це питання SO для розрізнення) в межах однієї потоку на рівні ОС, передаючи управління назад і назад один від одного. організовано.

Використовуючи волокна, зелені нитки або супроводи, зазвичай у вас є окремий стек на функцію. (Технічно не лише стек, а весь контекст виконання - це функція. Найголовніше, що CPU реєструється.) Для кожного потоку існує стільки стеків, скільки одночасно виконуваних функцій, і потік перемикається між виконанням кожної функції відповідно до логіки вашої програми. Коли функція працює до кінця, її стек руйнується. Отже, кількість та термін служби стеків є динамічними та не визначаються кількістю потоків на рівні ОС!

Зауважте, що я сказав: " зазвичай є окремий стек на функцію". Там ви обидва stackful і Stackless реалізації couroutines. Більшість реалізацій відрізняється stackful C ++ є Boost.Coroutine і Microsoft Закон про держзакупівлі «s async/await. (Однак, відновлювані функції C ++ (ака " asyncі await"), які були запропоновані на C ++ 17, швидше за все, будуть використовувати безпроблемні розробки.)

Пропозиція волокон до стандартної бібліотеки C ++ надходить. Також є кілька сторонніх бібліотек . Зелені нитки надзвичайно популярні в таких мовах, як Python та Ruby.


19

Мені є чим поділитися, хоча основні моменти вже висвітлюються.

Стек

  • Дуже швидкий доступ.
  • Зберігається в оперативній пам'яті.
  • Тут вивантажуються функціональні виклики разом з переданими локальними змінними та параметрами функції.
  • Простір звільняється автоматично, коли програма виходить із сфери застосування.
  • Зберігається в послідовній пам'яті.

Купи

  • Повільний доступ порівняно зі стеком.
  • Зберігається в оперативній пам'яті.
  • Тут зберігаються динамічно створені змінні, що пізніше потребує звільнення виділеної пам'яті після використання.
  • Зберігається там, де відбувається розподіл пам'яті, доступ до якого завжди вказується.

Цікава примітка:

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

1
лаконічний і чистий. приємно :)
ingconti

13

Оце Так! Стільки відповідей, і я не думаю, що одна з них зрозуміла правильно ...

1) Де і що вони (фізично в реальній пам'яті комп'ютера)?

Стек - це пам'ять, яка починається як найвища адреса пам’яті, що виділяється для зображення вашої програми, а потім зменшується звідти. Він зарезервований для виклику параметрів функції та для всіх тимчасових змінних, що використовуються у функціях.

Є дві купи: державна та приватна.

Приватна купа починається з 16-байтового кордону (для 64-бітних програм) або 8-байтового кордону (для 32-бітових програм) після останнього байта коду у вашій програмі, а потім збільшується звідти. Його також називають купою за замовчуванням.

Якщо приватна купа стає занадто великою, вона буде перекривати область стека, як і стек перекривати купу, якщо вона стає занадто великою. Оскільки стек починається з вищої адреси і працює вниз до нижчої адреси, при правильному злому ви можете зробити стек настільки великим, що він перекриє приватну область купи і перекриє область коду. Трюк тоді полягає в перекритті достатньої кількості кодової області, яку ви можете підключити до коду. Це зробити трохи непросто, і ви ризикуєте завершити програму, але це легко і дуже ефективно.

Загальнодоступна купа знаходиться у власному просторі пам’яті поза вашим графічним простором програми. Саме ця пам'ять буде зафіксована на жорсткому диску, якщо ресурсів пам’яті не вистачає.

2) Наскільки вони контролюються ОС або мовою виконання?

Стек управляє програмістом, приватною купою керує ОС, а публічною купою ніхто не керує, оскільки це служба ОС - ви робите запити, або вони надаються, або відмовляються.

2б) Яка сфера їх застосування?

Всі вони є глобальними для програми, але їхній вміст може бути приватним, загальнодоступним або глобальним.

2в) Від чого залежить розмір кожного з них?

Розмір стека та приватна купа визначаються параметрами виконання компілятора. Загальнодоступна купа ініціалізується під час виконання за допомогою параметра розміру.

2d) Що робить одного швидшим?

Вони не розроблені так, щоб бути швидкими, вони розроблені так, щоб бути корисними. Те, як програміст використовує їх, визначає, чи є вони "швидкими" чи "повільними"

REF:

https://norasandler.com/2019/02/18/Write-a-Compiler-10.html

https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap

https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate


8

Дуже багато відповідей вірні як поняття, але ми повинні зауважити, що стек потрібен апарату (тобто мікропроцесор), щоб дозволити викликати підпрограми (CALL на мові збірки ..). (Хлопці з OOP називатимуть це методами )

На стеці ви зберігаєте зворотні адреси та дзвінки → push / ret → pop керуються безпосередньо апаратними засобами.

Ви можете використовувати стек для передачі параметрів .. навіть якщо це повільніше, ніж використання регістрів (сказав би гуру мікропроцесора чи хороша книга BIOS 1980-х ...)

  • Без стека жоден мікропроцесор не може працювати. (ми не можемо уявити програму, навіть мовою складання, без підпрограм / функцій)
  • Без купи це може. (Програма збору мов може працювати без, оскільки купа - це концепція ОС, як malloc, тобто виклик ОС / Lib.

Використання стека швидше:

  • Є обладнання та навіть push / pop дуже ефективні.
  • malloc вимагає входу в режим ядра, використання блокування / семафору (або інших примітивів синхронізації), виконуючи якийсь код і керувати деякими структурами, необхідними для відстеження розподілу.

Що таке OPP? Ви маєте на увазі OOP ( об'єктно-орієнтоване_програмування )?
Пітер Мортенсен

Ви хочете сказати, що mallocце дзвінок ядра?
Пітер Мортенсен

1) так, вибачте .. OOP ... 2) malloc: я пишу коротко, вибачте ... malloc знаходиться в просторі користувача .. але може викликати інші дзвінки .... справа в тому, що використання купи МОЖЕ бути дуже повільним ...
ingconti

" Дуже багато відповідей вірні як поняття, але ми повинні зазначити, що стек потрібен апаратному засобу (тобто мікропроцесору), щоб дозволити виклик підпрограм (CALL на мові збірки ..) ". Ви плутаєте стек процесора (якщо він був у сучасному процесорі) та стеки виконання мов (один на нитку). Коли програмісти говорять про стек, це стек виконання потоків під час виконання, наприклад, стек потоків NET), ми не говоримо про стек процесора.
хвилини

1

Стек - це, по суті, легкодоступна пам'ять, яка просто керує своїми елементами як - добре - стек. На стек можуть потрапляти лише елементи, для яких розмір відомий заздалегідь . Це стосується чисел, рядків, булей.

Купи пам'ять для елементів якого ви не можете зумовлюють точний розмір і структуру . Оскільки об'єкти та масиви можуть бути вимкнено та змінені під час виконання, вони повинні перейти у купу.

Джерело: Academind


0

Дякую за дуже гарну дискусію, але як справжній ноб я цікавлюсь, де зберігаються інструкції? У ПОЧАТКУ вчені вирішували між двома архітектурами (фон NEUMANN, де все вважається ДАНИМИ та HARVARD, де область пам'яті була зарезервована для інструкцій, а інша - для даних). Зрештою, ми пішли з дизайном фон Неймана, і тепер все вважається "однаковим". Це ускладнило мене, коли я вивчав складання https://www.cs.virginia.edu/~evans/cs216/guides/x86.html, оскільки вони говорять про регістри та покажчики стеків.

Все, що вище, говорить про DATA. Я здогадуюсь, що оскільки інструкція - це визначена річ із певним слідом пам’яті, вона піде на стек, і тому всі регістри «тих», що обговорюються при складанні, знаходяться на стеку. Звичайно, тоді з'явилося об'єктно-орієнтоване програмування з інструкціями та даними, що потрапили в структуру, яка була динамічною, тож тепер інструкції зберігатимуться також у купі?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.