Programação, Delphi, OutOfMemory, MemoryLeak
Conceito
Nesta página são disponibilizados alguns exemplos de erros decorrentes do mal geranciamento de memória;
Access Violation
Erro mais comum que acontece ao tentar utilizar um objeto que já foi liberado da memória ou que não tenha sido instanciado.
Exemplo de variável não instanciada:
procedure CriaXML(pTransacao: TTransacao); var cXML: string; oGuarda: TGuarda; begin oGuarda := nil; try cXML := Self.ServicoNFe_CriarXMLNFeInterno(pTransacao, True); if cXML <> EmptyStr then begin oGuarda := Self.GuardaDAO.LoadGuarda(pTransacao); oGuarda.XMLSefaz := cXML; // <-- AccessViolation oGuarda.Exportado := False; Self.GuardaDAO.GravaGuarda(pTransacao, oGuarda); finally if Assigned(oGuarda) then FreeAndNil(oGuarda); end; end;
Neste exemplo se a variável cXML estiver vazia, o objeto oGuarda não será instanciado e ocorrerá AccessViolation ao tentar utilizar o objeto na linha " oGuarda.XMLSefaz := cXML; " :
Exemplo de variável destruída incorretamente:
procedure CriaXML(pTransacao: TTransacao); var cXML: string; oGuarda: TGuarda; begin oGuarda := nil; try cXML := Self.ServicoNFe_CriarXMLNFeInterno(pTransacao, True); oGuarda := Self.GuardaDAO.LoadGuarda(pTransacao); oGuarda.XMLSefaz := cXML; oGuarda.Exportado := False; FreeAndNil(oGuarda); Self.GuardaDAO.GravaGuarda(pTransacao, oGuarda); // <-- AccessViolation finally if Assigned(oGuarda) then end; end;
O mesmo erro de AccessViolation ocorrerá se liberar a variável mas tentar utilizá-lá em seguida :
Diferença entre .Free e FreeAndNil():
Apesar de ambas as funções liberarem o objeto da memória, há uma diferença entre as duas que devemos levar em consideração.
1. .Free
procedure CriaXML(pTransacao: TTransacao); var oGuarda: TGuarda; begin oGuarda := Self.GuardaDAO.LoadGuarda(pTransacao); oGuarda.Free; //Quando usamos .Free a variável ainda está apontado para o endereço da memória, mesmo se já foi liberado. Este ponteiro fica ativo durante todo o escopo da função. if Assigned(oGuarda) then // Neste caso Assigned(oGuarda) retornará TRUE! oGuarda.Exportado := False; // Access Violation nesta linha end;
Como o .Free não limpa o ponteiro da memória, Assigned() retornará true. :
Este comportamento é mantido até limparmos o ponteiro manualmente ":= nil" ou o escopo da função terminar. :
Ao usar .Free não ocorrerá vazamento de memória, porém é importante levar em consideração se a variável será utilizada novamente após o .Free
2. FreeAndNil()
procedure CriaXML(pTransacao: TTransacao); var oGuarda: TGuarda; begin oGuarda := Self.GuardaDAO.LoadGuarda(pTransacao); FreeAndNil(oGuarda); // FreeAndNil chama o .Free do objeto, mas também anula o ponteiro da memória. if Assigned(oGuarda) then // Neste caso Assigned(oGuarda) retornará FALSE oGuarda.Exportado := False; // Não entrará no if end;
Com o FreeAndNil temos certeza de liberar completamente o objeto.
3. Como utilizar .Free e FreeAndNil() de forma correta
procedure SalvaExportado(pTransacao: TTransacao); var oGuarda: TGuarda; begin // Dentro de um bloco Try.. Finally temos a certeza que o objeto será liberado oGuarda := TGuarda.Create(); try oGuarda.Exportado := True; oGuarda.Salvar; finally if Assigned(oGuarda) then // Como boa prática validamos se a variável foi instanciada para evitar qualquer erro de AccessViolation FreeAndNil(oGuarda); // Como vamos utilizar a variável oGuarda abaixo, o ideal é executar o FreeAndNil() end; if not Assigned(oGuarda) then // Neste caso Assigned(oGuarda) retornará FALSE begin oGuarda := TGuarda.Create(); oGuarda.Exportado := False; try oGuarda.Salvar; finally oGuarda.Free; // Como não utilizaremos mais a variável, não há problema de usar o .Free! end; end; end;
Invalid Pointer Operation
Conceito Este erro é menos comum, porém não menos importante.
Ocorre ao tentarmos utilizar um indice inexistente de uma lista. A lista pode ser de qualquer tipo, porém utilizaremos TObjectList como exemplo.
procedure SalvaExportado(pTransacao: TTransacao); var oEnderecoList: TObjectList<TEndereco>; begin oEnderecoList: TObjectList<TEndereco>.Create(); oEnderecoList.First.Logradouro := 'Rua fulano de tal'; // <-- Invalid Pointer Operation end;
Neste exemplo básico podemos ver como acontece o erro de invalid pointer.
Não foi adicionado nenhum objeto a lista, e mesmo assim tentamos acessar o primeiro indice.
OwnsObjects
Todo TObjectList tem uma flag OwnsObjects: Boolean.
Caso esta flag esteja como True, ao liberar a lista da memória, automaticamente irá percorrer os indices e liberar da memória os objetos contidos nos indices também!.
Caso esteja false, os objetos deverão ser liberados manualmente, caso contrário, vazamento de memória.
Quando se torna perigoso?
Recentemente estava ocorrendo vários erros de Invalid Pointer Operation em produção
O erro ocorria ao referenciar um objeto de uma lista diretamente para outro objeto.
function TServicoImportacao.CriaNota: TDocEmitido; begin {...} Result.EnderecoDeCobranca := Result.Participante.EnderecoList.First; // EnderecoList do objeto Participante (TParceiro) é um TObjectList<TEndereco>, neste caso com "OwnsObjects" setado para TRUE. Result.EnderecoDeEntrega := Result.Emitente.EnderecoList.First; // O mesmo ocorre no Emitente. Result.EnderecoDeFaturamento := Result.Emitente.EnderecoList.First; {...} end; destructor TDocEmitido.Destroy; begin {...} if Assigned(FEnderecoDeCobranca) then FreeAndNil(FEnderecoDeCobranca); // Aponta para o mesmo endereço de memória de FParticipante.EnderecoList.First. Aqui o objeto é liberado da memória. if Assigned(FParticipante) then FreeAndNil(FParticipante); // <-- Chama o destructor do TParceiro (ver abaixo); {...} end; destructor TParceiro.Destroy; begin if Assigned(FEnderecoList) then FreeAndNil(FEnderecoList); // <-- Invalid Pointer Operation. // Como OwnsObjects está true, ao destruir a lista, irá percorrer os indices e tentar destruí-los, // porém liberamos da memória ao executar FreeAndNil(FEnderecoDeCobranca) anteriormente. end;
Como evitar o erro? Nos fontes do middleware utilizamos a classe TTotaliObject para os objetos simples como TEndereco.
type TEndereco = class(TTotaliObject) {...} end;
Na classe TTotaliObject temos a função .Clone<T> que podemos usar para duplicar algum objeto de qualquer classe que herda de TTotaliObject.
Podemos utilizar o Clone<T> no indice da lista que queremos acessar
Assim a referência não será perdida e não ocorrerá o erro Invalid Pointer Operation ao liberar a lista da memória.
function TServicoImportacao.CriaNota: TDocEmitido; begin {...} Result.EnderecoDeCobranca := TTotaliObject.Clone<TEndereco>(Result.Participante.EnderecoList.First); Result.EnderecoDeEntrega := TTotaliObject.Clone<TEndereco>(Result.Emitente.EnderecoList.First); Result.EnderecoDeFaturamento := TTotaliObject.Clone<TEndereco>(Result.Emitente.EnderecoList.First); {...} end;