Quando é que um valor estado mutável libertado da pilha?
-
03-07-2019 - |
Pergunta
Em F # wikilivro sob encapsular mutável Estado seção, há um seguinte trecho de código .
> 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
Na primeira, ele parecia bastante fácil de engolir o fato de que, incrementos de valor counter
mutáveis ??toda incr
é invocado.
Mas depois de pensar sobre isso por algum tempo, o que eu não conseguia entender era quando counter
é libertado da pilha e também como counter
ainda se refere ao valor anterior antes de ser incrementado. Como é counter
que vive dentro de escopo de função incr
sobreviver através de várias chamadas de função?
Assim principais perguntas são:
- Quando é que
counter
libertado da pilha? - Não é
counter
um vazamento de memória?
Solução
A distinção entre 'escopo léxico' (onde um nome tem significado no texto de um programa) e 'vida' (duração de tempo de execução entre quando o objeto é criado e destruído) às vezes pode ser confuso, pois muitas vezes estes dois são altamente correlacionados . No entanto, a técnica demonstrada por este exemplo é comum em linguagens funcionais: você dá um detalhe de implementação de um pequeno escopo léxico (que esconde os detalhes de implementação de chamadores), mas estender sua vida útil, capturando-o em um fechamento (de modo que a sua vida se torna o tempo de vida do objecto encerra - neste caso a função 'incr'). Esta é uma maneira comum de fazer encapsulamento em programação funcional (em contraste com a técnica de encapsulamento usual de público / privado em aulas de programação orientada a objetos).
Agora, neste exemplo particular, parece que 'incr' é uma função de nível superior, o que significa que seu valor dura a vida do programa (ou sessão interativa se digitação em fsi.exe). Você poderia chamar isso de 'vazamento', mas isso depende de intenção. Se você tem alguma contra-id único que você precisa para a vida inteira de todo o seu programa, então você vai ter que armazenar esse contador varable em algum lugar que tem a duração de todo o programa. Assim, ou este é 'um vazamento' ou 'um por recurso de design', dependendo de como 'incr' será usado (você vai precisar usar essa função para todo o resto do programa?). Em qualquer caso, o ponto-chave aqui é que 'incr' detém recursos de memória, por isso, se você não vai precisar desses recursos para sempre, você deve providenciar para o encerramento referenciado por 'incr' para se tornar inacessível quando ele não é mais necessário. Comumente isso pode ser, tornando-o local para alguma outra função, por exemplo.
let MyComplicatedFuncThatNeedsALocalCounter args =
let incr =
// as before
// other code that uses incr
// return some result that does not capture incr
Outras dicas
Neste caso, incr
é uma função de nível superior (implementado como um campo estático, se não me engano.) Ele mantém um fecho que por sua vez tem uma referência a essa célula ref counter
nomeado. Enquanto existir este encerramento, a célula ref
é mantido na memória.
Agora ligação deste nível superior vai realmente nunca se lixo coletado, pois é um estático readonly campo. (Em termos C #). Se você, no entanto, têm fechamentos como que com um limitado vitalícia (ligada localmente ou em um objeto), a célula ref
será liberado quando o fecho é lixo coletado.
Contador está livre da pilha quando incr não está acessível. Não é um vazamento de memória por causa da coleta de lixo.