Переменная жизнь
Что происходит с переменной, когда строка выполнения выходит за пределы блока кода? Например:
1 public void myMethod()
2 {
3 int number;
4 number = 5;
5 }
Итак, мы объявляем и устанавливаем переменную. Когда он выходит за пределы блока кода (строка 5), что происходит с номером переменной?
Вот еще один пример создания экземпляра класса:
7 public void myMethod()
8 {
9 Customer myClient;
10 myClient = new Customer();
11 }
Когда он выходит за пределы блока кода (строка 11), что происходит с ссылкой на объект myClient?
Я думаю, в обоих случаях переменная выделяется, но когда она освобождается?
5 ответов
В качестве переменной это понятие на языке C#. Ничего "не происходит" с ним вне блока кода, потому что он находится внутри блока кода. Ничего не происходит со словом " слово" за пределами этого предложения.
Конечно, вы имеете в виду, что происходит с тем, чем переменная становится при запуске кода, но стоит помнить о различии, потому что при рассмотрении этого вопроса мы переходим к уровням, где переменные не такие, как в C#.
В обоих случаях код превращается в CIL, а затем в машинный код при запуске.
CIL может немного отличаться. Например, вот как выглядит первый при компиляции в режиме отладки:
.method public hidebysig instance void myMethod () cil managed
{
.locals init ([0] int32) // Set-up space for a 32-bit value to be stored
nop // Do nothing
ldc.i4.5 // Push the number 5 onto the stack
stloc.0 // Store the number 5 in the first slot of locals
ret // Return
}
А вот как это выглядит при компиляции для выпуска:
.method public hidebysig instance void myMethod () cil managed
{
ret // Return
}
Так как значение не используется, компилятор удаляет его как бесполезную ненужную информацию и просто компилирует метод, который немедленно возвращает значение.
Если компилятор не удаляет такой код, мы можем ожидать что-то вроде:
.method public hidebysig instance void myMethod () cil managed
{
ldc.i4.5 // Push the number 5 onto the stack
pop // Remove value from stack
ret // Return
}
Сборки отладки хранят вещи дольше, потому что их изучение полезно для отладки.
Когда сборки выпуска сохраняют вещи в массиве локальных объектов, они также с большей вероятностью повторно используют слоты в методе.
Затем это превращается в машинный код. Это было бы аналогично тому, как это будет работать, в том смысле, что оно будет либо производить число 5, хранить его локально (в стеке или в регистре), а затем снова избавляться от него, либо, в качестве альтернативы, ничего не делать, поскольку неиспользуемая переменная была удалена, (Возможно, даже не выполняя метод; метод может быть встроен, а затем, поскольку он ничего не делает, будет полностью удален).
С типом с конструктором происходит немного больше:
.method public hidebysig instance void myMethod () cil managed
{
.locals init ([0] class Temp.Program/Customer) // Set-up space for a reference to a Customer
nop // Do nothing.
newobj instance void SomeNamespace/Customer::.ctor() // Call Customer constructor (results in Customer on the stack)
stloc.0 // Store the customer in the frist slot in locals
ret // Return
}
.method public hidebysig instance void myMethod () cil managed
{
newobj instance void SomeNamespace/Customer::.ctor() // Call Customer constructor (results in Customer on the stack)
pop // Remove value from stack
ret // Return
}
Здесь оба вызывают конструктор, и даже сборка релиза делает это, потому что она должна гарантировать, что любые побочные эффекты все же случаются.
Там также происходит больше, если Customer
является ссылочным типом Если это тип значения, то все это хранится в стеке (хотя в нем могут быть поля, которые в свою очередь являются ссылочными типами). Если это ссылочный тип, то в стеке хранится ссылка на объект в куче. Когда в стеке больше нет таких ссылок, сборщик мусора не найдет его в цикле поиска, чтобы найти, какие объекты он не может собрать, и его можно собрать.
В версии выпуска никогда не может быть места в памяти или регистра, содержащего эту ссылку после возврата конструктора. На самом деле, может не быть ни одного, даже когда конструктор был запущен (если нет доступа к полю или другого неявного или явного использования этого), или он мог быть удален частично (после того, как такой доступ завершился), поэтому сборка мусора может произойти до того, как конструктор даже закончил.
Скорее всего, он будет зависать в куче памяти в течение некоторого времени после возврата метода, потому что сборщик мусора еще не запущен.
Предполагая, что вы работаете в Debug, без оптимизации:
Когда он выходит за пределы блока кода (строка 5), что происходит с номером переменной?
Значение выходит из стека после выхода из метода. Тот факт, что типы значений находятся в стеке, является деталью реализации, и вы не должны на это полагаться. Если это был тип значения, который был полем в class
это не будет жить в стеке, но в куче.
Когда он выходит за пределы блока кода (строка 5), что происходит с номером переменной?
Если предположить, Customer
это class
и не struct
и не имеет реализованного финализатора (который меняет ход вещей), у него больше не будет никакого объекта, ссылающегося на него после строки 9. Как только GC включится (в произвольное, недетерминированное время), он увидит его как пригодного для сбора. и пометить его как на этапе пометки. Как только начинается фаза развертки, она освобождает занятую память.
При потере ссылки на поле структурного типа память освобождается (в стеке). Для справочного типа это сложнее. Если объект (класс) больше не используется и ссылка на него потеряна, он помечается для удаления сборщиком мусора. Если ничего не меняется, этот объект удаляется при следующей сборке мусора.
Если вы не хотите ждать, пока GC запустит свой метод автоматически, вы можете сделать это самостоятельно, вызвав метод GC.Collect()
PS Перед уничтожением объекта класса и освобождением памяти (если он реализует интерфейс IDisposable) он вызывает три метода один за другим:
1. Dispose() 2. Finalize() 3. ~ctor()
В C# вы можете использовать два из них: dispose() и finalize(). Утилизация обычно используется для освобождения управляемых ресурсов (например, FileStream
или же Threads
) когда Finalize больше подходит для написания логики для освобождения неуправляемых ресурсов.
Чтобы изменить логику для object.Finalize()
метод - поместите свою логику в ~ctor(), но будьте осторожны с этим, так как это может привести к серьезным неисправностям.
В первом случае number
является типом значения и будет храниться в стеке. Как только вы выйдете из метода, он больше не будет существовать. И никакого освобождения не происходит, пространство стека будет просто использоваться для других целей.
Во втором случае, так как Customer
(Я бы предположил) является ссылочным типом, myClient
будет хранить ссылку на экземпляр в стеке. Как только вы покидаете метод, эта ссылка больше не существует. А это значит, что экземпляр в итоге будет собираться мусором.
В 99% случаев ответ "это не имеет значения". Единственное, что имеет значение, это то, что он больше не доступен для вас.
Вы не должны слишком часто заботиться об оставшихся 1%. Это не легко упростить это достаточно для разумного ответа на SO. Единственное, что я могу сказать достаточно просто:
- Как только переменная больше не будет использоваться в будущем, для компилятора или среды выполнения совершенно законно делать все, что угодно, черт побери:)
Обратите внимание, что это ничего не говорит о области видимости - C# на самом деле не заботится о области видения. Сфера предназначена для того, чтобы помочь вам кодировать, а не помогать компилятору (хотя метод и более высокая область видимости, безусловно, помогают во время компиляции).
Опять же, в большинстве случаев вам все равно, что будет дальше. Основными исключениями из этого являются:
- Использование неуправляемых ресурсов. Обычно хорошей идеей является удаление неуправляемых ресурсов детерминистически. Все классы, которые инкапсулируют неуправляемые ресурсы, имеют
Dispose
способ справиться с этим. Вы можете использоватьusing
заявление, чтобы помочь с этим. - Узкие места в производительности - если профилирование показывает, что вы теряете память / ЦП из-за нецелесообразного освобождения, вы, возможно, захотите немного помочь.
- Сохранение ссылки на объект вне области видимости. Это довольно легко случайно предотвратить сбор чего-то, что больше не используется, но все еще имеет ссылку. Это основная причина утечек памяти в управляемых приложениях.
Кроме того, если вы немного поиграете с этим, обратите внимание, что по умолчанию подключение отладчика будет немного мешать вам. Например, местные жители будут оставаться в живых до тех пор, пока не закончится их область действия - это, конечно, совершенно законно, и это помогает при отладке.