Параметры С# по ссылке и сборку мусора .net

Я пытаюсь выяснить тонкости системы сбора мусора .NET, и у меня есть вопрос, связанный с ссылочными параметрами С#. Если я правильно понимаю, переменные, определенные в методе, хранятся в стеке и не зависят от сбора мусора. Итак, в этом примере:

public class Test
{
 public Test()
 {
 }

 public int DoIt()
 {
  int t = 7;
  Increment(ref t);
  return t;
 }

 private int Increment(ref int p)
 {
  p++;
 }
}

возвращаемое значение DoIt() будет равно 8. Так как местоположение t находится в стеке, тогда эта память не может быть собрана или сжата мусором, а ссылочная переменная в Increment() всегда укажет на правильное содержимое t.

Однако предположим, что имеем:

public class Test
{
 private int t = 7;

 public Test()
 {
 }

 public int DoIt()
 {
  Increment(ref t);
  return t;
 }

 private int Increment(ref int p)
 {
  p++;
 }
}

Теперь t сохраняется в куче, поскольку это значение конкретного экземпляра моего класса. Разве это не проблема, если передать это значение в качестве эталонного параметра? Если я передать т в качестве опорного параметра, р будет указывать на текущее местоположение т. Однако, если сборщик мусора перемещает этот объект во время компактного устройства, разве это не испортит ссылку на t в Increment()? Или сборщик мусора обновляет даже ссылки, созданные путем передачи ссылочных параметров? Должен ли я вообще об этом беспокоиться? Единственное упоминание о том, что проблема с памятью, сжимаемой в MSDN (которую я могу найти), связана с передачей управляемых ссылок на неуправляемый код. Надеюсь, это потому, что мне не нужно беспокоиться о каких-либо управляемых ссылках в управляемом коде.:)

Ответы

Ответ 1

Если я правильно понимаю, переменные, определенные в методе, хранятся в стеке и не зависят от сбора мусора.

Это зависит от того, что вы подразумеваете под "затронутым". Переменные в стеке являются корнями сборщика мусора, поэтому они, безусловно, влияют на сбор мусора.

Так как местоположение t находится в стеке, тогда эта память не может быть собрана или уплотнена мусором, а ссылочная переменная в Increment() всегда укажет на правильное содержимое t.

"Невозможно" - это странное слово для использования здесь. Точка использования стека в первую очередь заключается в том, что стек используется только для данных, которые никогда не нужно уплотнять и срок службы которых всегда известен, поэтому никогда не нужно собирать мусор. Вот почему мы используем стек в первую очередь. Кажется, вы ставите телегу перед лошадью. Позвольте мне повторить это, чтобы убедиться, что это понятно: причина, по которой мы храним этот материал в стеке, состоит в том, что его не нужно собирать или уплотнять, потому что его жизнь известна. Если бы его жизнь не была известна, то она пошла бы на кучу. Например, локальные переменные в блоках итератора идут по куче по этой причине.

Теперь t сохраняется в куче, поскольку это значение конкретного экземпляра моего класса.

Правильно.

Разве это не проблема, если передать это значение в качестве эталонного параметра?

Неа. Это прекрасно.

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

Угу. Хотя я предпочитаю думать, что p является псевдонимом для переменной t.

Однако, если сборщик мусора перемещает этот объект во время компактности, разве это не испортит ссылку на t в Increment()?

Неа. Сборщик мусора знает об управляемых ссылках; поэтому их называют управляемыми ссылками. Если gc перемещает объект, управляемая ссылка остается в силе.

Если вы указали фактический указатель на t, используя небезопасный код, вам потребуется привязать контейнер t к месту, чтобы сборщик мусора знал, что он не перемещает его. Вы можете сделать это, используя фиксированный оператор в С#, или создав GCHandle для объекта, который вы хотите вывести.

обновляет ли сборщик мусора даже ссылки, созданные путем передачи ссылочных параметров?

Угу. Это было бы довольно хрупким, если бы не было.

Нужно ли вообще об этом беспокоиться?

Неа. Вы думаете об этом как о неуправляемом программисте на С++ - С++ заставляет вас выполнять эту работу, но С# этого не делает. Помните, что весь смысл модели управляемой памяти - освободить вас от необходимости думать об этом.

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

Ответ 2

Нет, вам не нужно беспокоиться об этом. В основном метод вызова (DoIt) имеет "живую" ссылку на экземпляр Test, что предотвратит сбор мусора. Я не уверен, можно ли его уплотнять, но я подозреваю, что это возможно, когда GC может определить, какие ссылки переменных являются частью перемещаемых объектов.

Другими словами - не беспокойтесь. Может ли он быть уплотнен или нет, это не должно вызывать проблем.

Ответ 3

Именно это вы упоминали в последнем предложении. GC будет перемещать все необходимые ссылки, когда он уплотняет кучу (за исключением ссылок на неуправляемую память).

Обратите внимание, что использование стека или кучи связано с переменной экземпляра, имеющей значение или ссылочный тип. Типы значений (структуры и "простые" типы, такие как int, double и т.д.) Всегда находятся в стеке, классы всегда находятся в куче (то, что находится в стеке, является ссылкой - указателем - на выделенную память для экземпляра).

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