[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!


 

18 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!

  3. Boa tarde, gostei do POST, porém eu tenho este erro constantemente em alguns clientes, e trechos que não são meus, por exemplo Error reading Labelx.Caption . “Access violation”…. É apenas um label no form, com formatação padrão, sem funções nem evento, e sem nenhuma mudança em runtime.

    1. Olá, Márcio, tudo certo?
      Este erro parece estar relacionado com o arquivo DFM, que armazena a definição das propriedades dos componentes inseridos no formulário. Já encontrei erros semelhantes quando o projeto foi migrado de uma versão muito antiga do Delphi para uma versão mais nova. Em outros casos, o arquivo DFM estava corrompido e foi necessário uma restauração.
      Você notou se esse erro ocorre em apenas um formulário? Se possível, envie um e-mail para contato@andrecelestino.com com mais detalhes, como a versão do Delphi.

      Fico no aguardo. Abraço!

  4. Seu exemplo não funcionou, deu erro:
    [Error] AccessViolation.dpr(11): Undeclared identifier: ‘MainFormOnTaskbar’ e não permite ver a call stack

    1. Olá, Luciano.
      Acredito que você está tentando abrir este projeto no Delphi 7 ou inferior.
      O projeto foi desenvolvido em uma versão mais recente do Delphi (Seattle, pelo que eu me lembre).
      De qualquer forma, para testar este exemplo na versão que você está utilizando, basta remover a linha abaixo do arquivo DPR:

      Application.MainFormOnTaskbar := True;
  5. “View > Debug Windows > Call Stack”
    Show esta função do Delphi. Não conhecia e me salvou hoje.
    Valeu demais.

    1. Olá, Frederico.
      Acesse o menu Project > Options e depois em Compiling. Verifique se a opção “Use debug .dcus” está selecionada.
      Além disso, vale lembrar que o Call Stack só irá apresentar informações se o projeto estiver sendo executado em modo de depuração (F9) e também se a opção “Debug” estiver marcada em Build Configurations.
      Abraço!

  6. Bom dia André,
    Segui com atenção V. exemplo. Concretamente estou a referir-me ao “Access Violation” e aos passos para procurar identificar as suas causas: View-> Debug -> Windows-> Call Stack; tudo como afirma, porém, a janela a seguir não abre! Baixei a última versão do EUREKALOG, na esperança que ajudasse algo mais, mas não consegui grande coisa! Apenas dá para perceber que há um erro, provavelmente, um bug no UserControl que pendura o meu programa na hora da saída.

    // Fechando janelas abertas;
    if MDIChildCount > 0 then
      with MainForm do
      begin
        Application.ProcessMessages;
        for i := MDIChildCount-1 downto 0 do
        begin
          MDIChildren[I].Close; // Todas com: Action := caFree;
        end;
        Application.ProcessMessages;
      end;
      // Seguindo-se:
      FreeAndNil(UserControl1); // Com e sem: dá sempre o mesmo erro!
      Action := caFree;
      Application.Terminate; // Com e sem dá sempre o mesmo erro!

    Com o Eurekalog apenas dá para perceber que se trata de um problema lá com First chance exception at $0040ADE5. Exception class $C0000005 with message ‘access violation at 0x0040ade5: read of address 0x06928384’.

    1. Olá, Mário, tudo bem?
      Bom, eu precisaria analisar melhor o contexto do código, mas, pela mensagem de erro, aparentemente há um objeto inexistente sendo acessado. Isso normalmente ocorre quando tenta-se acessar um objeto que já foi destruído.
      Fiquei curioso quando ao Call Stack. Essa janela deveria mostrar as informações das chamadas. Não sei o que houve para a janela não aparecer para você.
      Mário, experimente inserir breakpoints no código e descobrir exatamente qual a linha que está gerando o erro. Isso irá ajudá-lo na identificação do problema.

      Abraço!

  7. Tenho um sistema em Delphi, e preciso de uma ajuda. Usando o programa Process Explorer vejo que existe uma thread que está consumindo muita memoria com picos de 1 segundo.
    Exemplo: 100MB de consumo, 1 segundo depois sobe para 500MB e 1 segundo depois volta aos 100MB, porém ao verificar qual é a thread ele não exibe no LOG. Quando dou um kill ou suspend a threads o consumo fica estável.
    É possível colocar algo que poderia mostrar essa threads que não é exibida no log?

    1. Olá, Lucio, bom dia.
      Infelizmente tenho pouco conhecimento com o Process Explorer, portanto, eu tentaria encontrar esse problema do consumo através de depuração (debugging). Você sabe qual é a classe thread que está executando esse processamento? Se souber, experimente adicionar alguns breakpoints e acompanhar a execução dos métodos da thread. Talvez há uma falha no gerenciamento de memória, como Memory Leaks, leituras excessivas de I/O ou um grande volume de dados em memória.
      Outra alternativa é utilizar o relatório de Memory Leaks nativo do Delphi, adicionando a linha abaixo no arquivo de projeto (DPR):

      ReportMemoryLeaksOnShutdown := True;

      Ao fechar a aplicação, o Delphi irá exibir uma mensagem com os possíveis vazamentos de memória que ocorreram durante a execução. Este relatório pode ajudá-lo a rastrear o “culpado” por esse pico de processamento.

      Abraço!

Deixe uma resposta

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