No Delphi 7, por que posso atribuir um valor a um const?

Copiei alguns códigos Delphi de um projeto para outro e descobri que ele não é compilado no novo projeto, embora tenha sido feito no antigo. O código é algo como isto:

procedure TForm1.CalculateGP(..) const Price : money = 0; begin ... Price := 1.0; ... end; 

Assim, no novo projeto, o Delphi reclama que “o lado esquerdo não pode ser atribuído a” – compreensível! Mas este código compila no projeto antigo. Então minha pergunta é: por quê ? Existe um switch de compilador para permitir que os consts sejam reatribuídos? Como isso funciona? Eu achei consts foram substituídos por seus valores em tempo de compilation?

Você precisa ativar as constantes digitadas atribuíveis. Projeto -> Opções -> Compilador -> Constantes digitadas atribuíveis

Além disso, você pode adicionar {$J+} ou {$WRITEABLECONST ON} ao arquivo pas, o que provavelmente é melhor, já que funcionará mesmo se você mover o arquivo para outro projeto.

As constantes inferidas por tipo podem ser apenas valores escalares – isto é, coisas como inteiros, duplos, etc. Para esses tipos de constantes, o compilador realmente substitui o símbolo da constante pelo valor da constante sempre que os encontra em expressões.

As constantes digitadas, por outro lado, podem ser valores estruturados – matrizes e registros. Esses caras precisam de armazenamento real no executável – ou seja, eles precisam ter armazenamento alocado para eles de tal forma que, quando o sistema operacional carrega o executável, o valor da constante digitada é fisicamente contido em algum local na memory.

Para explicar porque, historicamente, as constantes digitadas no início do Delphi e seu predecessor, o Turbo Pascal, são graváveis ​​(e, portanto, variables ​​globais essencialmente inicializadas), precisamos voltar aos dias do DOS.

O DOS é executado em modo real, em termos x86. Isso significa que os programas têm access direto à memory física sem que qualquer MMU faça mapeamentos virtuais físicos. Quando os programas têm access direto à memory, nenhuma proteção de memory está em vigor. Em outras palavras, se houver memory em qualquer endereço, ela é legível e gravável no modo real.

Portanto, em um programa Turbo Pascal para DOS com uma constante tipada, cujo valor é alocado em um endereço na memory em tempo de execução, essa constante tipada será gravável. Não há hardware MMU ficando no caminho e impedindo o programa de escrever para ele. Da mesma forma, como Pascal não tem noção de ‘const’ness que o C ++ tenha, não há nada no sistema de tipos para pará-lo. Muita gente aproveitou isso, já que o Turbo Pascal e o Delphi não tinham inicializado variables ​​globais como um recurso.

Passando para o Windows, existe uma camada entre endereços de memory e endereços físicos: a unidade de gerenciamento de memory. Esse chip pega o índice da página (uma máscara deslocada) do endereço de memory que você está tentando acessar e procura os atributos dessa página em sua tabela de páginas . Esses atributos incluem legível, gravável e, para chips x86 modernos, sinalizadores não executáveis. Com esse suporte, é possível marcar seções do .EXE ou .DLL com atributos que, quando o carregador do Windows carrega a imagem executável na memory, atribui atributos de página apropriados para páginas de memory que mapeiam para páginas de disco nessas seções.

Quando a versão para Windows de 32 bits do compilador Delphi apareceu, fazia sentido tornar as coisas constantes realmente constantes, já que o sistema operacional também tinha esse recurso.

  1. Por que: Como nas versões anteriores do Delphi, as constantes digitadas eram designadas por padrão para preservar a compatibilidade com versões mais antigas, nas quais eram sempre graváveis ​​(Delphi 1 até o início de Pascal).
    O padrão agora foi alterado para tornar as constantes realmente constantes…

  2. Interruptor do compilador: {$ J +} ou {$ J-} ​​{$ WRITEABLECONST ON} ou {$ WRITEABLECONST OFF}
    Ou nas opções de projeto para o compilador: verifique as constantes digitadas atribuíveis

  3. Como funciona: Se o compilador pode calcular o valor em tempo de compilation, ele substitui a const pelo seu valor em todo lugar no código, caso contrário, ele mantém um ponteiro para uma área de memory contendo o valor, que pode ser gravável ou não.
  4. veja 3.

Como Barry disse, as pessoas se aproveitaram de consts; Uma das maneiras como isso foi usado foi para acompanhar as instâncias de singleton. Se você observar uma implementação clássica de singleton, verá isso:

  // Example implementation of the Singleton pattern. TSingleton = class(TObject) protected constructor CreateInstance; virtual; class function AccessInstance(Request: Integer): TSingleton; public constructor Create; virtual; destructor Destroy; override; class function Instance: TSingleton; class procedure ReleaseInstance; end; constructor TSingleton.Create; begin inherited Create; raise Exception.CreateFmt('Access class %s through Instance only', [ClassName]); end; constructor TSingleton.CreateInstance; begin inherited Create; // Do whatever you would normally place in Create, here. end; destructor TSingleton.Destroy; begin // Do normal destruction here if AccessInstance(0) = Self then AccessInstance(2); inherited Destroy; end; {$WRITEABLECONST ON} class function TSingleton.AccessInstance(Request: Integer): TSingleton; const FInstance: TSingleton = nil; begin case Request of 0 : ; 1 : if not Assigned(FInstance) then FInstance := CreateInstance; 2 : FInstance := nil; else raise Exception.CreateFmt('Illegal request %d in AccessInstance', [Request]); end; Result := FInstance; end; {$IFNDEF WRITEABLECONST_ON} {$WRITEABLECONST OFF} {$ENDIF} class function TSingleton.Instance: TSingleton; begin Result := AccessInstance(1); end; class procedure TSingleton.ReleaseInstance; begin AccessInstance(0).Free; end;