Почему в книгах .Net говорится о распределении стека и кучи памяти?

36

Кажется, что каждая книга .net говорит о типах значений по сравнению со ссылочными типами и указывает на то, что (часто неправильно) указывает состояние, в котором хранится каждый тип - куча или стек. Обычно это в первых нескольких главах и представлено как какой-то очень важный факт. Я думаю, что это даже покрыто на сертификационных экзаменах . Почему стек и куча имеют значение для начинающих разработчиков .Net? Вы распределяете вещи, и это просто работает, верно?

Greg
источник
11
У некоторых авторов просто плохое мнение о том, что важно преподавать новичкам, а что не имеет никакого отношения к шуму. В книге, которую я недавно видел, первое упоминание о модификаторах доступа уже включало защищенный внутренний , который я никогда не использовал за 6 лет C # ...
Тимви
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 # есть такие функции, как «fixed», «stackalloc» и так далее.

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

Эрик Липперт
источник
2
Спасибо за ответ, Эрик. Ваши последние сообщения в блоге на эту тему - вот что на самом деле побудило меня опубликовать вопрос.
Грег
13

Вы, ребята, все упускаете суть. Причина, почему различие стека / кучи важно из-за области видимости .

struct S { ... }

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

Как только x выходит из области видимости, созданный объект категорически исчезает . Это только потому, что он расположен в стеке, а не в куче. В «...» части метода нет ничего, что могло бы изменить этот факт. В частности, любые присваивания или вызовы методов могли только создавать копии структуры S, но не создавать новые ссылки на нее, чтобы она могла продолжать жить.

class C { ... }

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

Совершенно другая история! Поскольку x теперь находится в куче , его объект (то есть сам объект , а не его копия) вполне может продолжать жить после того, как x выходит из области видимости. Фактически, единственный способ, которым он не будет продолжать жить, - это если х - единственная ссылка на него. Если присваивания или вызовы метода в части «...» создали другие ссылки, которые все еще «живы» к тому времени, когда x выходит из области видимости, тогда этот объект будет продолжать жить.

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

JoelFan
источник
Я не уверен, что видел этот аргумент, представленный ранее в книгах вместе с обсуждением стека / кучи, но он хороший. +1
Грег
2
Из-за способа, которым C # генерирует замыкания, код в ...может привести xк преобразованию в поле сгенерированного компилятором класса и, таким образом, превзойти указанную область видимости. Лично я нахожу неприятным идею неявного поднятия, но разработчики языка, кажется, идут на это (в отличие от требования, чтобы любая переменная, которая была поднята, имела что-то в своем объявлении, чтобы указать это). Чтобы гарантировать корректность программы, часто необходимо учитывать все ссылки, которые могут существовать на объект. Зная, что к тому времени, когда подпрограмма вернется, никакая копия переданной ссылки не останется полезной.
суперкат
1
Что касается «структур в стеке», то правильным утверждением будет то, что если место хранения объявлено как structType foo, то место хранения fooсодержит содержимое его полей; если fooв стеке, то и его поля. Если fooесть в куче, то и его поля. Если fooв сетевом Apple II, так же, как и его поля. В противоположность этому , если fooбыли типом класса, он будет проводить либо null, или ссылка на объект. Единственная ситуация, в которой fooможно сказать, что тип класса содержит поля объекта, была бы, если бы оно было единственным полем класса и содержало ссылку на себя.
суперкат
+1, мне нравится ваше понимание здесь, и я думаю, что оно действительно ... Однако я не чувствую, что это оправдание того, почему книги освещают эту тему так глубоко. Похоже, то, что вы объяснили здесь, могло бы заменить эти 3 или 4 главы упомянутой книги и быть НАСТОЛЬКО более полезным.
Фрэнк V
1
насколько я знаю, структуры не должны и не всегда идут в стек.
Сара
5

Что касается ПОЧЕМУ они охватывают тему, я согласен с @Kirk, что это важная концепция, которую вы должны понять. Чем лучше вы знаете механизмы, тем лучше вы сможете создавать великолепные приложения, которые работают без сбоев.

Теперь Эрик Липперт, похоже, согласен с вами, что эта тема не совсем правильно освещена большинством авторов. Я рекомендую вам прочитать его блог, чтобы добиться отличного понимания того, что находится под капотом.

Федя
источник
2
Что ж, пост Эрика подчеркивает, что все, что вам нужно знать, - это явные характеристики значений и ссылочных типов, и не следует ожидать, что реализация останется прежней. Я думаю, что довольно напрашивается вопрос, что существует эффективный способ перестроить C # без стека, но его точка зрения верна: это не является частью языковой спецификации. Поэтому единственная причина использовать это объяснение, которое я могу придумать, заключается в том, что это аллегория, полезная для программистов, которые знают другие языки, в частности C. До тех пор, пока они знают, что это аллегория, многое из литературы не ясно.
Джереми
5

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

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

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

Тем не менее, я думаю, что сказать намного больше, чем «память управляется». В противном случае люди могут сделать неправильные выводы.

back2dos
источник
2

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

Кирк Фернандес
источник
2
В управляемых языках вы должны знать разницу между типом значения и ссылочным типом, но помимо этого легко обернуться вокруг оси, думая о том, как она управляется изнутри. Смотрите здесь пример: stackoverflow.com/questions/4083981/…
Роберт Харви
Я должен согласиться с Робертом
Разница между выделенной кучей и выделенным стеком именно то, что объясняет разницу между типом значения и ссылочным типом.
Джереми
3
@ Джереми: Не совсем. См. Blogs.msdn.com/b/ericlippert/archive/2010/09/30/…
Роберт Харви
1
Джереми, различие между распределением кучи и стека не может объяснить различное поведение между типами значений и ссылочными типами, потому что бывают моменты, когда оба типа значений и ссылочные типы находятся в куче, и все же они ведут себя по-разному. Более важная вещь для понимания (например), когда вам нужно использовать передачу по ссылке для ссылочных типов против типов значений. Это зависит только от «это тип значения или ссылочный тип», а не «это в куче».
Тим Гудман
2

Могут быть некоторые крайние случаи, когда это может иметь значение. Размер стека по умолчанию составляет 1 мг, а куча - несколько гигабайт. Поэтому, если ваше решение содержит большое количество объектов, вы можете исчерпать пространство стека, имея достаточно места в куче.

Тем не менее, по большей части это довольно академично.

GrumpyMonkey
источник
Да, но я сомневаюсь, что любая из этих книг старается объяснить, что сами ссылки хранятся в стеке - поэтому не имеет значения, если у вас много ссылочных типов или множество типов значений, у вас все еще может быть переполнение стека.
Джереми
0

Как вы говорите, C # должен абстрагироваться от управления памятью, а распределение кучи и стека - это детали реализации, о которых, теоретически, разработчик не должен знать.

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

Обратите внимание, что книги о Python или JavaScript не имеют большого значения из этого, если они даже упоминают об этом. Это потому, что все либо выделено кучей, либо неизменным, что означает, что разные семантики копирования никогда не вступают в игру. В этих языках работает абстракция памяти, в C # она протекает.

JacquesB
источник