Как ссылки на объекты С# представлены в памяти/во время выполнения (в среде CLR)?

Мне любопытно узнать, как ссылки на объекты С# представлены в памяти во время выполнения (в .NET CLR). Некоторые вопросы, которые приходят на ум, следующие:

  • Сколько памяти занимает ссылка на объект? Различается ли это, если определено в области действия класса по сравнению с областью действия метода? Где он живет, зависит от этой области (стек против кучи)?

  • Каковы фактические данные, поддерживаемые в ссылке на объект? Это просто адрес памяти, указывающий на объект, на который он ссылается, или есть еще что-нибудь? Различается ли это в зависимости от того, определено ли это в рамках класса или метода?

  • Те же вопросы, что и выше, но на этот раз, когда речь идет о ссылке на ссылку, например, когда ссылка на объект передается методу по ссылке. Как изменить ответы на 1 и 2?

Ответ 1

Этот ответ наиболее легко понят, если вы понимаете указатели C/С++. Указатель - это просто адрес памяти некоторых данных.

  • Ссылка на объект должна быть размером указателя, который обычно составляет 4 байта на 32-битном ЦП, и 8 байтов на 64-битном ЦП. Это то же самое независимо от того, где оно определено. Там, где он живет, зависит от того, где он определен. Если это поле класса, оно будет находиться в куче объекта, частью которого он является. Если это статическое поле, оно расположено в специальном разделе кучи, которое не подлежит сбору мусора. Если это локальная переменная, она живет в стеке.

  • Ссылка на объект - это просто указатель, который может быть визуализирован как int или long, содержащий адрес объекта в памяти. Это то же самое, независимо от того, где оно определено.

  • Это реализуется как указатель на указатель. Данные одинаковые - только адрес памяти. Однако на данном адресе памяти нет объекта. Вместо этого есть другой адрес памяти, который является исходной ссылкой на объект. Это позволяет изменять ссылочный параметр. Как правило, параметр исчезает, когда его метод завершается. Поскольку ссылка на объект не является параметром, то изменения в этой ссылке сохраняются. Ссылка на ссылку исчезнет, ​​но не ссылка. Это является целью передачи контрольных параметров.

Одна вещь, которую вы должны знать, типы значений сохраняются на месте (нет адреса памяти, вместо этого они хранятся непосредственно там, где будет адрес памяти - см. № 1). Когда они передаются методу, создается копия и эта копия используется в методе. Когда они передаются по ссылке, передается адрес памяти, который определяет тип значения в памяти, позволяя ему изменять.

Изменить: Как указал Длев, эти ответы не являются жестким и быстрым правилом, поскольку нет правила, которое говорит, что так оно и должно быть..NET может свободно выполнять эти вопросы, но он хочет. Это наиболее вероятный способ его реализации, поскольку именно так процессор Intel работает внутри, поэтому использование любого другого метода, вероятно, будет неэффективным.

Надеюсь, я не слишком вас смутил, но не стесняйтесь спрашивать, нужно ли вам разъяснение.

Ответ 2

Кучи и стеки .NET Это тщательное рассмотрение того, как работают стек и куча.

С# и многие другие языки с ООП, использующие кучу в общем справочном-говорящем, используют Ручки не указатели для ссылок в этом контексте (С# также может использовать указатели!). Упорядочивающие аналоги работают для некоторых общих понятий, но эта концептуальная модель ломается для таких вопросов. См. Eric Lippert отличную запись на эту тему Ручки не являются адресами

Нельзя сказать, что дескриптор - это размер указателя. (хотя он может совпадать). Ручки - это псевдонимы для объектов, не обязательно, чтобы они были формальным адресом к объекту.

В этом случае CLR использует реальные адреса для дескрипторов: из приведенной выше ссылки:

... CLR фактически реализует ссылки на управляемые объекты как адреса объектам, принадлежащим сборщику мусора, но это осуществление подробности.

Итак, дескриптор, вероятно, имеет 4 байта в 32-битной архитектуре и 8 байтов в архитектуре с 64 байтами, но это не "точно", и это происходит не напрямую из-за указателей. Следует отметить, что в зависимости от реализации компилятора и диапазонов адресов некоторые типы указателей могут отличаться по размеру.

Со всем этим контекстом вы, вероятно, можете моделировать это по аналогии с указателем, но важно понимать, что Handles не являются адресами. CLR может изменить это, если захочет в будущем, и потребители CLR не должны знать ничего лучше.

Конечный привод этой тонкой точки:

Это указатель на С#:

int* myVariable;

Это С# Handle:

object myVariable;

Они не совпадают.

Вы можете делать такие вещи, как математика, указатели, которые вы не должны делать с помощью Handles. Если ваш дескриптор реализуется как указатель, и вы используете его, как если бы это был указатель, вы неправильно используете Handle в некоторых случаях, которые могут вызвать у вас проблемы позже.