21

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

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

所以在新项目中,Delphi 抱怨“左侧不能分配给”——可以理解!但是这段代码在旧项目中编译。所以我的问题是,为什么?是否有允许重新分配 const 的编译器开关?这甚至是如何工作的?我认为 const 在编译时被它们的值替换了?

4

4 回答 4

30

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

您还可以将{$J+}或添加{$WRITEABLECONST ON}到 pas 文件,这可能会更好,因为即使您将文件移动到另一个项目,它也会起作用。

于 2008-09-08T00:46:42.083 回答
27

类型推断的常量只能是标量值 - 即整数、双精度值等。对于这些类型的常量,编译器确实会在表达式中遇到常量时将常量的符号替换为常量的值。

另一方面,类型化的常量可以是结构化值——数组和记录。这些家伙需要在可执行文件中实际存储——即他们需要为他们分配存储空间,这样,当操作系统加载可执行文件时,类型化常量的值物理上包含在内存中的某个位置。

为了解释为什么在历史上早期 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是有意义的,因为操作系统也有这个特性。

于 2008-09-08T02:41:20.467 回答
11
  1. 为什么:因为在以前的 Delphi 版本中,类型化的常量默认是可分配的,以保持与旧版本的兼容性,它们总是可写的(Delphi 1 到早期的 Pascal)。
    现在已更改默认值以使常量真正保持不变……</p>

  2. 编译器开关:{$J+} 或 {$J-} {$WRITEABLECONST ON} 或 {$WRITEABLECONST OFF}
    或在编译器的项目选项中:检查可分配的类型常量

  3. 它是如何工作的:如果编译器可以在编译时计算该值,它会在代码中的任何地方用它的值替换 const,否则它会保存一个指向保存该值的内存区域的指针,该内存区域可以被设为可写或不可写。
  4. 见 3。
于 2008-09-16T00:46:20.457 回答
2

就像 Barry 说的,人们利用了 consts;其中一种使用方式是跟踪单例实例。如果您查看经典的单例实现,您会看到:

  // 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;
于 2008-09-19T06:33:42.983 回答