Когда значение изменяемого состояния освобождается от кучи?

StackOverflow https://stackoverflow.com/questions/817958

Вопрос

В F # WikiBook в разделе Инкапсулирующее Изменяемое состояние раздел, там есть следующий фрагмент кода.

> let incr =
    let counter = ref 0
    fun () ->
        counter := !counter + 1
        !counter;;

val incr : (unit -> int)

> incr();;
val it : int = 1

> incr();;
val it : int = 2

> incr();;
val it : int = 3

Поначалу казалось достаточно легким смириться с тем фактом, что изменчивый counter значение увеличивается каждый раз incr вызывается.

Но, поразмыслив об этом некоторое время, чего я не мог понять, так это того, когда counter освобождается от кучи, а также как counter все еще ссылается на предыдущее значение перед увеличением.Как это counter , который живет внутри incr область действия функции сохраняется при нескольких вызовах функций?

Итак, основными вопросами являются:

  • Когда происходит counter освободился от кучи?
  • Это не так counter утечка памяти?
Это было полезно?

Решение

Различие между "лексической областью действия" (где имя имеет значение в тексте программы) и "временем жизни" (продолжительность времени выполнения между созданием объекта и уничтожением) иногда может сбивать с толку, поскольку часто эти два понятия сильно коррелируют.Однако техника, продемонстрированная в этом примере, распространена в функциональных языках:вы предоставляете деталям реализации небольшую лексическую область видимости (которая скрывает детали реализации от вызывающих), но продлеваете ее срок службы, фиксируя ее в замыкании (так что ее время жизни становится временем жизни заключающего объекта - в данном случае функции 'incr').Это распространенный способ выполнения инкапсуляции в функциональном программировании (в отличие от обычной техники инкапсуляции public / private в классах в объектно-ориентированном программировании).

Теперь, в этом конкретном примере, похоже, что 'incr' - это функция верхнего уровня, что означает, что ее значение сохраняется в течение всего срока службы программы (или интерактивного сеанса, если вводить в fsi.exe).Вы могли бы назвать это "утечкой", но это зависит от намерения.Если у вас есть какой-то счетчик уникальных идентификаторов, который вам нужен на весь срок службы всей вашей программы, то вам придется хранить этот счетчик в переменной где - то что это длится в течение всей программы.Таким образом, либо это "утечка", либо "из-за конструктивной особенности", в зависимости от того, как будет использоваться "incr" (нужно ли будет использовать эту функцию для всей остальной части программы?).В любом случае, ключевым моментом здесь является то, что 'incr' содержит ресурсы памяти, поэтому, если вам не понадобятся эти ресурсы навсегда, вы должны сделать так, чтобы закрытие, на которое ссылается 'incr', стало недоступным, когда оно больше не понадобится.Обычно это может быть сделано путем придания ему локального значения для какой-либо другой функции, например

let MyComplicatedFuncThatNeedsALocalCounter args =
    let incr = 
        // as before
    // other code that uses incr
    // return some result that does not capture incr

Другие советы

В данном случае, incr является функцией верхнего уровня (реализована как статическое поле, если я не ошибаюсь.) Она содержит замыкание, которое, в свою очередь, имеет ссылку на эту ячейку ref с именем counter.Пока это замыкание существует, ref ячейка хранится в памяти.

Теперь эта привязка верхнего уровня действительно никогда не будет собирать мусор, поскольку это статическое поле, доступное только для чтения.(в терминах C #).Если у вас, однако, есть подобные замыкания с ограниченный время жизни (привязанное локально или в объекте), ref ячейка будет освобождена, когда после закрытия будет собран мусор.

счетчик освобождается из кучи, когда incr больше не доступен.Это не утечка памяти из-за сборки мусора.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top