[Delphi] Access Violation! O que fazer?

[Delphi] Access Violation! O que fazer?Olá, leitores! Wow! Sejam bem-vindos ao novo visual do blog! 🙂
Bom, como já faz um tempo que não elaboro artigos sobre Delphi, hoje é dia de publicar mais um tópico técnico! Imagine que você esteja realizando alguns testes funcionais no seu software, quando, de repente, ocorre um Access Violation! Você sabe como proceder para rastrear (e resolver) o problema?

 

O tal do Access Violation é um dilema para qualquer programador Delphi e virou até motivo de piada. Alguns ironizam, dizendo “Acesso violento” ou “Excel Violation”, haha. O problema é que, por ser uma mensagem de erro muito genérica, os Access Violations realmente nos deixam de cabelo em pé, principalmente em situações quando parece não ter um motivo aparente. A saída é respirar fundo e fazer uso de alguns recursos do Delphi para identificar a linha de código que está causando a violação.

Pois bem, e por falar em recursos, o Delphi disponibiliza uma ferramenta que captura a pilha de chamadas de métodos e nos ajuda bastante nessa rastreabilidade, chamada Call Stack.
Já ouviu falar? Não? Então, na próxima vez que você se deparar com um Access Violation, acesse o menu View > Debug Windows > Call Stack e observe a janela que será exibida. É uma lista que representa um “histórico” de chamadas de métodos, permitindo acompanhar o fluxo de operações que o sistema está executando até o levantamento da exceção. Confira o exemplo abaixo:

Exemplo de Call Stack

Ao analisar o Call Stack acima, é possível identificar que tudo se inicia no evento de clique de um botão e percorre vários outros métodos, de baixo para cima, até a parada do sistema. Ao clicar duas vezes no nome dos métodos exibidos na janela, o Delphi nos leva à linha de código que os chamam. Opa, assim fica mais simples rastrear o erro, não é?
Baixe o exemplo neste link, execute-o no Delphi e observe, pelo Call Stack, que a origem do problema está no método “ConsultarDados”.

Por quê?
Os erros de Access Violation, em grande maioria, são causados por acessos a endereços de memória que não existem, como, por exemplo, utilizar um objeto que não foi instanciado ou que já foi destruído. Considere o código abaixo:

var
  ListaCidades: TStringList;
begin
  ListaCidades.Add('São Paulo');
  ListaCidades.Add('Maringá');
  ListaCidades.Add('Florianópolis');
  ...
  Memo1.Lines.Text := ListaCidades.Text;
end;

 

Ao executar esse código, o que acontece? Access Violation! O motivo do erro é visível: o objeto do tipo TStringList não foi instanciado antes de ser utilizado, ou seja, esse objeto não existe na memória e não pode ser referenciado. Como solução, se adicionarmos a linha abaixo no início do método, o erro será evitado:

ListaCidades := TStringList.Create;

 

Agora, uma reflexão: quando usamos uma ferramenta, como uma chave de fenda, o correto é devolvê-la no mesmo lugar onde estava, certo? Isso é bom senso. Pensando assim, já que criamos um objeto, também precisamos destruí-lo após a utilização, caso contrário, o espaço alocado na memória para esse objeto permanecerá preenchido. Isso pode causar quedas de desempenho, uso alto de memória RAM, Memory Leaks, ou, talvez, os erros de “Out of Memory”.
A destruição de objetos no Delphi pode ser realizada com o FreeAndNil:

FreeAndNil(ListaCidades);

Mas, cuidado! Após destruir o objeto, ele é liberado da memória e não deve mais ser utilizado! Como exemplo, o código abaixo também resultaria em um Access Violation:

...
FreeAndNil(ListaCidades);
 
Memo1.Text := ListaCidades.Text; // neste momento, o objeto não existe mais

 

Como posso evitar os Access Violations?
Embora seja um erro de runtime, ou seja, que ocorre somente em tempo de execução, existem algumas técnicas de codificação que podem reduzir o risco da apresentação de Access Violations no sistema. A primeira delas é o Assigned – função que identifica se o objeto passado por parâmetro está alocado na memória. Ao utilizá-lo, podemos evitar que uma operação com um objeto inexistente seja executada. No código a seguir, só obtenho as informações da lista caso o objeto exista:

if Assigned(ListaCidades) then
  Memo1.Lines.Text := ListaCidades.Text;

Claro, é importante mencionar que não é nada apropriado utilizar o Assigned em todos os lugares que fazem referência a objetos, já que tornaria o código redundante e desnecessário. Utilize-o em pontos estratégicos, como em métodos que são chamados por vários locais no software. Um destes locais, talvez, pode não utilizar os objetos internos do método, portanto, se não utilizarmos o Assigned para verificar a existência, ocorrerá os erros de violação de acesso.

A segunda técnica é o esqueleto do Try..Finally, no qual já comentei no artigo sobre técnicas de tratamento de exceções. Adquira o hábito de escrever a estrutura completa do Try..Finally antes de partir para a codificação da regra do método. Além de garantir a destruição do objeto, esse hábito colabora para a confiabilidade do aplicativo e evita eventuais Access Violations, caso o objeto seja uma variável de classe.

begin
  ListaCidades := TStringList.Create;
  try
 
  finally
    FreeAndNil(ListaCidades);
  end;
end;

 

A terceira técnica é a utilização do destructor da classe TObject para liberar todos objetos que foram instanciados, visando o desempenho e integridade do sistema. Atente-se que o destructor também é útil para desligar eventos, redefinir propriedades e desvincular Interfaces, quando necessário. Este método deve ser declarado como override (sobrescrito) na seção public da classe:

public
  desctructor Destroy; override;
 
...
 
destructor TForm1.Destroy;
begin
  // desliga eventos
  DataSet1.AfterPost := nil;
 
  // desvincula Interfaces
  IControlador := nil;
 
  // destrói objetos instanciados na classe
  FreeAndNil(ObjetoClasse1);
  FreeAndNil(ObjetoClasse2);
 
  // redefine propriedades
  ClasseConfiguracoes.AtivarLog := True;
 
  // invoca os procedimentos da classe base
  inherited;
end;

Na minha opinião, a atividade de instanciar e destruir os objetos de forma apropriada (e consciente!) é um diferencial de um desenvolvedor, que, na verdade, não deixa de ser uma questão de pura responsabilidade.

 

Aproveitando o contexto deste artigo, vale apresentar o EurekaLog, um componente para Delphi que captura exceções no software e elabora um log muito bem detalhado, facilitando ainda mais a rastreabilidade do código defeituoso. Além disso, é possível configurá-lo para enviar o log para o e-mail do desenvolvedor no momento em que a exceção ocorre. Este envio pode ser ajustado para utilizar uma thread separada, dispensando a intervenção do usuário.

 

Obrigado pela atenção, leitores!
Abraço!


 

Compartilhe!
Share on FacebookTweet about this on TwitterShare on LinkedInShare on Google+Pin on PinterestEmail this to someone

6 comentários

  1. Boa tarde André, primeiramente gostaria de lhe dar os parabens pelo blog, todos os artigos que li são muito bons e queria aproveitar para lhe pedir (se for possivel, é claro) um artigo sobre conexoes via socket em delphi. Sou programador junior e estou com algumas duvidas principalmente no tratamento de conexoes do lado do servidor. desde já agradeço pela atenção.

    1. Olá, Leonardo!

      Obrigado pelo feedback sobre o blog! Isso me motiva bastante para continuar escrevendo!
      Leonardo, eu já tenho uma pauta de temas para escrever na sequência mas, mesmo assim, posso adicionar o tópico de sockets no Delphi.
      Assim que possível, elaboro um artigo!

      Abraço!

  2. procedure TFicha.Importar;
    var
      lnLoop     : Integer;
      loProduto  : TDocumentoDetalheBean;
      loNegocio  : TDocumentoPAFFichaNGC;
      lcTxtToPDV : String;
      lcTexto    : String;
    begin
      try
        loNegocio := TDocumentoPAFFichaNGC.Create;
        try
          loNegocio.CriarDocBean;

    Está dando “Access Violation” na hora que crio a instância. Alguma dica do que pode ser? Grato desde já pelas dicas e atenção.

    1. Olá, Robertson, tudo bem? Obrigado pelo comentário.
      Se o Access Violation ocorre ao criar o objeto, o erro provavelmente está no construtor (create) da classe TDocumentoPAFFichaNGC.
      Se você tem conhecimento em depuração (debug) no Delphi, adicione um breakpoint no construtor dessa classe e acompanhe a execução de cada linha até o erro ocorrer. Dessa forma ficará mais fácil identificar o problema.

      Abraço!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Preencha o campo abaixo * Time limit is exhausted. Please reload CAPTCHA.