четверг, 4 ноября 2010 г.

Правда о значимых типах

Правда о значимых типах - Невероятные приключения в коде - MSDN Blogs

Если вы читаете мой блог какое-то время, то вы должны знать, что меня беспокоит миф о том, что «значимые типы (value types) располагаются в стеке». К сожалению, в нашей собственной документации, так и во многих книгах, существует масса примеров прямо или косвенно поддерживающих этот миф. Я выступаю против него по нескольким причинам:
1. Обычно он неправильно выражается: нужно говорить «экземпляры значимых типов могут располагаться в стеке», вместо более распространенной фразы: «значимые типы всегда располагаются в стеке».
2. В большинстве случаев это неважно. Мы приложили множество усилий для того, чтобы сделать наше «управляемое» окружение (managed environment) таким, чтобы использование конкретного типа хранилища было скрыто от пользователя. В отличие от других языков, где ради корректности вы должны знать, располагается ли данный объект в стеке или в куче.
3. Это выражение не полное. А как насчет ссылок? Ссылки не являются значимыми типами и не являются экземплярами ссылочных типов, они являются значениями. Они должны где-то храниться. Хранятся они в стеке или в куче? Почему никто не говорит об этом? Просто потому, что у них нет соответствующего типа в системе типов C#? Это не является причиной для их игнорирования.
В прошлом, чтобы развеять этот миф, я говорил, что правильное высказывание должно быть следующим: «в реализации языка C# от Microsoft при использовании CLR для рабочих станций, значимые типы располагаются в стеке, когда значением является локальная или временная переменная, на которую не производится замыкание (closed-over) из лямбда-выражения или анонимного метода, тело метода не является блоком итератора (iterator block) и JIT-компилятор решил не помещать значение в один из регистров».
Куча незнакомых слов в этом предложении может показаться поразительной, но все они необходимы:
· Реализация языка программирования C# от других производителей может выбрать другие стратегии распределения памяти для временных переменных; не существует требований к языку программирования, в которых было бы сказано, что структура данных под названием «стек» должна применяться для хранения локальных значений значимых типов.
· У нас есть множество версий CLI, которые запускаются на встроенных системах, в веб-браузерах и т.п. Некоторые из них могут запускаться на экзотическом аппаратном обеспечении. Я понятия не имею, какие стратегии выделения памяти применяются в подобных версиях CLI. Насколько я знаю, некоторое оборудование вообще может не поддерживать такого понятия как «стек». Или может содержать несколько стеков для одного потока. Или все может храниться в куче.
· Лямбда-выражения и анонимные методы превращают локальные переменные в поля, выделенные в куче; они больше не располагаются в стеке.
· В текущей реализации языка для рабочих станций блоки итераторов также превращают локальные переменные в поля, выделенные в куче. В этом нет необходимости! Мы можем решить реализовывать блоки итераторов как сопрограммы (coroutines), выполняемые в волокнах (fiber) с выделенным стеком. В этом случае локальные переменные значимого типа могут располагаться в стеке волокон.
· Обычно люди забывают, что управление памятью не ограничивается «стеком» и «кучей». Регистры не располагаются ни в стеке, ни в куче, и совершенно законно расположить значимый тип в регистре, если существует регистр соответствующего размера. И почему важно знать, когда что-то располагается в стеке и не важно, когда что-то располагается в регистре? И наоборот, если понимание алгоритма управления регистрами JIT-компилятора неважно большинству пользователей, так почему алгоритм выделения объектов в стеке – важен?
Высказав все это много раз за последние несколько лет, я понял, что фундаментальная проблема заключается в ошибочной вере в то, что система типов имеет какое-либо отношение к стратегии выделения памяти. Просто неверно, что выбор использования стека или кучи имеет какое-либо отношение к типу сущности, располагаемой в памяти. Правда заключается в том, что выбор механизма распределения памяти имеет отношение только к требованию времени жизни области памяти.
Когда вы посмотрите на это с такой точки зрения, все сразу станет гораздо понятнее. Давайте разобьем предыдущую фразу на несколько декларативных высказывания:
· Существует три типа значений: (1) экземпляры значимых типов, (2) экземпляры ссылочных типов, и (3) ссылки. (Код на языке C# не может манипулировать экземплярами ссылочных типов напрямую; он всегда делает это с помощью ссылок. В небезопасном коде (unsafe code) указатели рассматриваются как значимые типы с целью определения требований к хранению их значений.)
· Существуют «хранилища», которые могут хранить значения.
· Каждое значение, с которым работает программа, располагается в некотором хранилище.
· Каждая ссылка (за исключением нулевой ссылки) ссылается на хранилище.
· Каждое хранилище обладает собственным «временем жизни» (lifetime). Т.е. периодом времени, в течение которого данные, находящиеся в этом хранилище являются корректными.
· Время между началом выполнения определенного метода, возвращением значения из этого метода или генерацией исключения в нем называется «активационным периодом» (activation period) выполнения метода.
· Код внутри метода может требовать использование некоторого хранилища. Если требуемое время жизни хранилища превосходит активационный период метода, тогда говорят, что хранилище «долгоживущее» (“long lived”), в противном случае хранилище называется «короткоживущим» (“short lived”). (Заметьте, что когда метод M вызывает метод N, методу M требуется хранилище для хранения параметров, передаваемых методу N и для значений, возвращаемых из метода N).
Теперь мы переходим к деталям реализации. В реализации языка C# от компании Microsoft, с использованием CLR справедливо следующее:
· Существует три типа хранилищ: стек, куча и регистры.
· Долгоживущие (long-lived) хранилища всегда располагаются в куче.
· Короткоживущие хранилища находятся в стеке или регистрах.
· Бывают ситуации, когда компилятору или среде исполнения необходимо определить, является ли конкретное хранилище долгоживущим или короткоживущим. В этом случае, благоразумно рассматривать такое хранилище как долгоживущее. В частности, хранилища для экземпляров ссылочных типов всегда считаются долгоживущими, даже когда можно доказать, что объекты короткоживущие. Таким образом, экземпляры ссылочных типов всегда располагаются в куче.
И теперь все становится на свои места:
· Мы видим, что по сути ссылки и экземпляры значимых типов аналогичны с точки зрения расположения в памяти; они располагаются в стеке, в регистрах или в куче, в зависимости от того, должно ли хранилище, в котором они располагаются, быть долгоживущим или короткоживущим.
· Часто бывает, что элементы массивов, поля ссылочных типов, локальные переменные блоков итераторов, локальные переменные, захваченные лямбда-выражением или анонимным методом должны жить дольше активационного периода метода, которому впервые потребовалось использовать это хранилище. И даже в тех редких случаях, когда их время жизни короче активационного периода метода, бывает сложно или невозможно написать компилятор, который бы знал об этом. Поэтому мы должны быть консервативными и всегда в таких случаях использовать управляемую кучу.
· Часто путем анализа во время компиляции можно определить, что локальные переменные или временные значения не используются после завершения активационного периода метода, и, таким образом, они могут рассматриваться как короткоживущие и располагаться в стеке или в регистрах.
Когда вы забудете эту безумную идею о том, что тип значения имеет какое-то отношение к хранилищу, становится значительно проще думать об этом. Конечно, я имею в виду следующее: вам не нужно думать об этом, пока вы не работаете с небезопасным кодом или выполняете сложное взаимодействие с неуправляемым кодом. Пусть компилятор и среда времени выполнения заботятся о времени жизни хранилища; это то, для чего они предназначены.
Оригинал статьи

1 комментарий:

varocherdagenhart комментирует...

Situs Judi Slot Online Deposit Pulsa Tanpa Potongan
Situs Judi Slot 토토배당률보기 Online bet surface area Deposit Pulsa Tanpa Potongan 돈포차 Paling bet 분석 Paling Situs Judi online deposit pulsa, dominoqq, 토토 사이트 casino online sbobet, judi bola, slot88.