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

时间:2020-03-05 18:49:06  来源:igfitidea点击:

我将一些Delphi代码从一个项目复制到另一个项目,发现它在新项目中无法编译,尽管在旧项目中可以编译。代码看起来像这样:

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

因此在新项目中,Delphi抱怨"无法将左侧分配给"是可以理解的!但是此代码在旧项目中编译。所以我的问题是,为什么?是否有编译器开关允许const重新分配?那怎么工作?我以为const在编译时已被其值替换了?

解决方案

回答

我们需要打开可分配的类型常量。
项目->选项->编译器->可分配的类型常量

我们也可以在pas文件中添加{$ J +}或者{$ WRITEABLECONST ON},这可能会更好,因为即使将文件移至另一个项目也可以使用。

回答

类型推断的常量只能是标量值,例如整数,双精度数等。对于这些常量,只要在表达式中满足常量的值,编译器的确会用常量的值替换常量的符号。

另一方面,类型化常量可以是结构化值数组和记录。这些人需要在可执行文件中实际存储,即他们需要为它们分配存储,以便在OS加载可执行文件时,键入常量的值实际上包含在内存中的某个位置。

为了解释为什么从历史上讲,早期的Delphi及其早期版本Turbo Pascal中的类型常量是可写的(因此本质上是初始化的全局变量),我们需要回到DOS时代。

DOS使用x86实模式运行。这意味着程序可以直接访问物理内存,而无需任何MMU进行虚拟物理映射。当程序可以直接访问内存时,则没有内存保护功能。换句话说,如果在任何给定地址处都有内存,则它在实模式下既可读写。

因此,在具有类型常量的DOS的Turbo Pascal程序中,其值在运行时分配到内存中的地址中,该类型常量将是可写的。没有硬件MMU妨碍我们编写程序。同样,由于Pascal没有C ++具有的"常量"概念,因此类型系统中没有任何东西可以阻止我们。很多人都利用了这一点,因为Turbo Pascal和Delphi当时还没有将全局变量初始化为功能。

转到Windows,内存地址和物理地址之间存在一层:内存管理单元。该芯片获取我们要访问的内存地址的页面索引(移位的掩码),并在其页面表中查找此页面的属性。这些属性包括可读,可写,对于现代x86芯片,这些属性包括不可执行的标志。有了此支持,便可以使用属性标记.EXE或者.DLL的各个部分,以便Windows加载程序将可执行映像加载到内存中时,它为映射到这些部分内的磁盘页面的内存页面分配适当的页面属性。

当32位Windows版本的Delphi编译器问世时,使类似const的东西真正成为const是有意义的,因为OS也具有此功能。

回答

  • 原因:因为在Delphi的早期版本中,默认情况下可分配类型常量以保持与始终可写的旧版本(Delphi 1到Pascal早期)的兼容性。现在已更改默认值,以使常数真正恒定
  • 编译器开关:{$ J +}或者{$ J-}​​ {$ WRITEABLECONST ON}或者{$ WRITEABLECONST OFF}或者在编译器的项目选项中:检查可分配的类型化常量
  • 工作原理:如果编译器可以在编译时计算该值,则它将在代码中的任何地方用其值替换const,否则它将持有指向该值的存储区的指针,该指针可以被设置为可写或者不可写。
  • 见3.

回答

就像巴里所说的那样,人们利用了const。使用此方法的一种方法是跟踪单例实例。
如果我们查看经典的单例实现,则会看到以下内容:

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