简体   繁体   English

在Delphi 7中,为什么我可以为const赋值?

[英]In Delphi 7, why can I assign a value to a const?

I copied some Delphi code from one project to another, and found that it doesn't compile in the new project, though it did in the old one. 我将一些Delphi代码从一个项目复制到另一个项目,并发现它不能在新项目中编译,尽管它在旧项目中编译。 The code looks something like this: 代码看起来像这样:

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

So in the new project, Delphi complains that "left side cannot be assigned to" - understandable! 所以在新项目中,Delphi抱怨“左侧无法分配” - 可以理解! But this code compiles in the old project. 但是这段代码在旧项目中编译。 So my question is, why ? 所以我的问题是, 为什么 Is there a compiler switch to allow consts to be reassigned? 是否有编译器开关允许重新分配consts? How does that even work? 这怎么样? I thought consts were replaced by their values at compile time? 我认为在编译时它们的值被替换了?

You need to turn assignable typed constants on. 您需要打开可分配的类型常量。 Project -> Options -> Compiler -> Assignable typed Constants 项目 - >选项 - >编译器 - >可分配的类型常量

Also you can add {$J+} or {$WRITEABLECONST ON} to the pas file, which is probably better, since it'll work even if you move the file to another project. 此外,您可以将{$J+}{$WRITEABLECONST ON}到pas文件中,这可能更好,因为即使您将文件移动到另一个项目它也能正常工作。

Type-inferred constants can only be scalar values - ie things like integers, doubles, etc. For these kinds of constants, the compiler does indeed replace the constant's symbol with the constant's value whenever it meets them in expressions. 类型推断的常量只能是标量值 - 即整数,双精度等等。对于这些常量,编译器确实用常量的值替换常量的符号,只要它在表达式中遇到它们。

Typed constants, on the other hand, can be structured values - arrays and records. 另一方面,键入的常量可以是结构化值 - 数组和记录。 These guys need actual storage in the executable - ie they need to have storage allocated for them such that, when the OS loads the executable, the value of the typed constant is physically contained at some location in memory. 这些人需要在可执行文件中实际存储 - 即他们需要为他们分配存储,这样当操作系统加载可执行文件时,类型化常量的值实际上包含在内存中的某个位置。

To explain why, historically, typed constants in early Delphi and its predecessor, Turbo Pascal, are writable (and thus essentially initialized global variables), we need to go back to the days of DOS. 为了解释为什么历史上早期Delphi及其前身Turbo Pascal中的类型常量是可写的(因此基本上是初始化的全局变量),我们需要回到DOS的时代。

DOS runs in real-mode, in x86 terms. DOS以实时模式运行,x86术语。 This means that programs have direct access to physical memory without any MMU doing virtual-physical mappings. 这意味着程序可以直接访问物理内存,而无需任何MMU进行虚拟物理映射。 When programs have direct access to memory, no memory protection is in effect. 当程序可以直接访问内存时,没有内存保护功能生效。 In other words, if there is memory at any given address, it is both readable and writable in real-mode. 换句话说,如果在任何给定地址处存在存储器,则它在实模式下都是可读写的。

So, in a Turbo Pascal program for DOS with a typed constant, whose value is allocated at an address in memory at runtime, that typed constant will be writable. 因此,在具有类型常量的DOS的Turbo Pascal程序中,其值在运行时在内存中的地址处分配,该类型常量将是可写的。 There is no hardware MMU getting in the way and preventing the program from writing to it. 没有硬件MMU阻碍并阻止程序写入它。 Similarly, because Pascal has no notion of 'const'ness that C++ has, there is nothing in the type system to stop you. 同样,因为Pascal没有C ++所具有的'const'ness概念,所以类型系统中没有任何东西可以阻止你。 A lot of people took advantage of this, since Turbo Pascal and Delphi did not at that time have initialized global variables as a feature. 很多人都利用了这一点,因为Turbo Pascal和Delphi当时没有将全局变量初始化为一个特征。

Moving on to Windows, there is a layer between memory addresses and physical addresses: the memory management unit. 继续使用Windows,内存地址和物理地址之间有一层:内存管理单元。 This chip takes the page index (a shifted mask) of the memory address you're trying to access, and looks up the attributes of this page in its page table . 该芯片获取您尝试访问的内存地址的页面索引(移位掩码),并在其页面表中查找此页面的属性。 These attributes include readable, writable, and for modern x86 chips, non-executable flags. 这些属性包括可读,可写和现代x86芯片,非可执行标志。 With this support, it's possible to mark sections of the .EXE or .DLL with attributes such that when the Windows loader loads the executable image into memory, it assigns appropriate page attributes for memory pages that map to disk pages within these sections. 通过此支持,可以使用以下属性标记.EXE或.DLL的各个部分:当Windows加载程序将可执行映像加载到内存中时,它会为映射到这些部分中的磁盘页的内存页分配适当的页面属性。

When the 32-bit Windows version of the Delphi compiler came around, it thus made sense to make const-like things really const, as the OS also has this feature. 当32位Windows版本的Delphi编译器出现时,因此操作系统也具有此功能,因此使const类似的东西变得非常有意义。

  1. Why: Because in previous versions of Delphi the typed constants were assignable by default to preserve compatibility with older versions where they were always writable (Delphi 1 up to early Pascal). 原因:因为在以前版本的Delphi中,默认情况下可以分配类型常量,以保持与旧版本的兼容性,在旧版本中它们始终可写(Delphi 1直到早期的Pascal)。
    The default has now been changed to make constants really constant… 现在已经更改了默认值以使常量真正保持不变...

  2. Compiler switch: {$J+} or {$J-} {$WRITEABLECONST ON} or {$WRITEABLECONST OFF} 编译器开关:{$ J +}或{$ J-}​​ {$ WRITEABLECONST ON}或{$ WRITEABLECONST OFF}
    Or in the project options for the compiler: check assignable typed Constants 或者在编译器的项目选项中:检查可分配的类型化常量

  3. How it works: If the compiler can compute the value at compile time, it replaces the const by its value everywhere in the code, otherwise it holds a pointer to a memory area holding the value, which can be made writeable or not. 工作原理:如果编译器可以在编译时计算该值,它会在代码中的任何位置用const值替换const,否则它会保存一个指向保存该值的内存区域的指针,该值可以写入或不可写。
  4. see 3. 见3。

Like Barry said, people took advantage of consts; 就像巴里所说,人们利用了竞争对手; One of the ways this was used, was for keeping track of singleton instances. 使用它的方法之一是跟踪单例实例。 If you look at a classic singleton implementation, you would see this : 如果你看一下经典的单例实现,你会看到:

  // 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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM