Чому .Net книги говорять про розподіл пам'яті стека та купи?


36

Схоже, що кожна .net книга розповідає про типи значень проти типів посилань і робить його точкою (часто неправильно) стану, де зберігається кожен тип - купа чи стек. Зазвичай це в перших кількох главах і подається як якийсь важливий факт. Я думаю, що це навіть охоплюється сертифікаційними іспитами . Чому стек проти купи навіть має значення для початківців розробників? Net? Ви виділяєте речі, і це просто працює, правда?


11
Деякі автори просто по-справжньому погано оцінюють те, що важливо навчити початківців і що не має значення шуму. У книзі, яку я побачив нещодавно, у першій згадці модифікаторів доступу вже включено захищений внутрішній , яким я ніколи не користувався за 6 років роботи C # ...
Timwi

1
Я здогадуюсь, що той, хто написав оригінальну документацію .Net для цієї частини, зробив це великою справою, і ця документація - це те, на чому автори спочатку ґрунтували свої книги, а потім просто залишилися навколо.
Грег

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

Інтерв'ю вантажного культу?
День

Відповіді:


37

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

Як зазначив Феде, Ерік Ліпперт має сказати про це дуже цікаві речі: http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx .

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

Для тих, хто вважає, що це все ще важливо з міркувань продуктивності: Які дії ви вчинили б, щоб перенести щось із купи в стек, якби ви виміряли речі і дізналися, що це має значення? Швидше за все, ви знайдете зовсім інший спосіб підвищення продуктивності для проблемної області.


6
Я чув, що в деяких реалізаціях рамок (зокрема компактний для Xbox) краще використовувати структури під час періоду візуалізації (сама гра), щоб скоротити збирання сміття. Ви все ще використовуєте звичайні типи в іншому місці, але попередньо розміщені, щоб GC не працював під час гри. Мова йде про єдину оптимізацію щодо стека проти купи, про яку я знаю в .NET, і вона досить специфічна для потреб компактних фреймворків та програм у режимі реального часу.
CodexArcanum

5
Я здебільшого погоджуюся з аргументом традиції. Багато досвідчених програмістів у якийсь момент, можливо, запрограмували на мові низького рівня, де цей матеріал мав значення, якби ви хотіли правильного та ефективного коду. Однак візьмемо для прикладу C ++ некеровану мову: Офіційна специфікація насправді не говорить про те, що автоматичні змінні повинні надходити в стек і т. Д. Стандарт C ++ розглядає стек і купу як деталі реалізації. +1
stakx

36

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

Я згоден повністю; Я це бачу весь час.

Чому книги .NET говорять про розподіл стека проти купи пам'яті?

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

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

Під час написання книги для початківців автор закономірно пояснює поняття в тому ж порядку, в якому їх вивчив. Це не обов’язково порядок, який має сенс для користувача. Нещодавно я був технічним редактором книги для початківців Скотта Дормана C # 4, і одна з речей, що мені сподобалась у тому, що Скотт вибрав досить розумне замовлення для тем, а не починаючи з того, що насправді є досить передовими темами управління пам’яттю.

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

Чому стек проти купи навіть має значення для початківців розробників .NET?

На мою думку, це не так. Що важливіше зрозуміти - це такі речі:

  • Чим відрізняється семантика копії між типом посилання та типом значення?
  • Як поводиться параметр "ref int x"?
  • Чому типи значення повинні бути незмінними?

І так далі.

Ви виділяєте речі, і це просто працює, правда?

Це ідеал.

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

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

Але це все прогресивні сценарії. В ідеалі програміст-початківець не повинен турбуватися ні про що з цього матеріалу.


2
Дякую за відповідь Ерік. Ваші останні публікації в блозі на цю тему насправді спонукали мене до запитання.
Грег

13

Ви, хлопці, все пропускаєте. Причина, чому важливо відрізняти стек / купу, - це масштаб .

struct S { ... }

void f() {
    var x = new S();
    ...
 }

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

class C { ... }

void f() {
     var x = new C();
     ...
}

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

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


Я не впевнений, що я бачив цей аргумент, представлений раніше в книгах, разом із дискусією стек / купа, але це добре. +1
Грег

2
Через те, як C # генерує закриття, код у ...може призвести xдо перетворення в поле класу, створеного компілятором, і, таким чином, перевищити вказану область. Особисто мені здається неприємною ідея неявного підйому, але мовні дизайнери, схоже, йдуть на її думку (на відміну від того, щоб вимагати, щоб будь-яка змінна, яка піднімається, мала щось у своїй декларації, щоб це вказати). Для забезпечення правильності програми часто необхідно враховувати всі посилання, які можуть існувати на об'єкт. Знаючи, що до моменту повернення звичайної програми жодна копія переданої посилання не залишиться корисною.
supercat

1
Що стосується "структури знаходяться в стеці", правильне твердження полягає в тому, що якщо місце зберігання оголошено як structType foo, місце зберігання fooмістить вміст його полів; якщо fooстоїть на стеці, то і поля його. Якщо fooце на купі, так це поля. якщо fooвін знаходиться в мережевій Apple II, тож поля його поля. Навпаки, якби це fooбув клас класу, він містив би nullабо посилання на об'єкт. Єдиною ситуацією, в якій fooможна сказати, що тип класу утримує поля об'єкта, було б, якщо б це було єдине поле класу, і воно посилалося б на себе.
supercat

+1, я люблю ваше розуміння тут, і я думаю, що це справедливо ... Однак я не вважаю, що це виправдання того, чому книги висвітлюють цю тему настільки глибоко. Здається, що те, що ви пояснили тут, може замінити ці 3 або 4 глави зазначеної книги і бути ДОПОМОГО більш корисними.
Франк V

1
з того, що я знаю, будови не повинні, ні вони дійсно завжди йдуть на стек.
сара

5

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

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


2
Ну і повідомлення Еріка вказує на те, що все, що вам потрібно знати, - це явні характеристики значення та еталонних типів, і не слід очікувати, що реалізація навіть залишиться такою ж. Я думаю, що досить важко запитати, що можна припустити, що існує ефективний спосіб повторно реалізувати C # без стека, але його суть є правильною: його не є частиною мовної специфікації. Тож єдиною причиною використання цього пояснення, яке я можу придумати, є те, що це алегорія корисна програмістам, які знають інші мови, зокрема C. Поки вони знають алегорію, про яку багато літератури не дає зрозуміти.
Джеремі

5

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

Я мало знаю про .NET, але наскільки я знаю, його JITted перед виконанням. Наприклад, JIT може виконати аналіз втечі, а що ні, і раптом у вас будуть предмети, що лежать на стеці або просто в деяких регістрах. Ви не можете цього знати.

Я припускаю, що деякі книги висвітлюють це просто тому, що автори приписують йому велике значення або тому, що вони припускають, що їх аудиторія робить (наприклад, якщо ви написали "C # для програмістів на C ++", ви, ймовірно, повинні висвітлювати цю тему).

Тим не менш, я думаю, що не можна сказати набагато більше, ніж "керованою пам'яттю". Інакше люди можуть зробити неправильні висновки.


2

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


2
В керованих мовах ви повинні знати різницю між типом значення та еталонним типом, але крім цього, його легко обернути навколо осі, думаючи про те, як це управляється під капотом. Дивіться тут приклад: stackoverflow.com/questions/4083981/…
Роберт Харві

Я повинен погодитися з Робертом

Різниця між розподіленою купою та виділеним стеком - саме те, що пояснює різницю між значеннями та еталонними типами.
Джеремі


1
Джеремі, різниця між розподілом купи та стека не може пояснити різну поведінку між типами значень та типовими типами, оскільки бувають випадки, коли і типи значень, і типи посилань знаходяться в купі, і все ж вони поводяться по-різному. Важливіше розуміти - це, наприклад, коли вам потрібно використовувати прохідні посилання для типів посилань і типів значень. Це просто залежить від "це тип значення або тип посилання", а не "чи це на купі".
Тім Гудман

2

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

Однак здебільшого це досить академічно.


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

0

Як ви кажете, C # повинен абстрагувати управління віддаленою пам’яттю, а розподіл купівлі проти стека - це деталі реалізації, про які теоретично розробникові не потрібно знати.

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

Зауважте, що книги про скажімо Python чи JavaScript не зроблять нічого із цього, якщо вони навіть згадують про це. Це тому, що все або виділяється купою, або незмінне, а це означає, що різні семантики копіювання ніколи не вступають у дію. У цих мовах працює абстракція пам’яті, в C # вона є герметичною.

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