3

我发现 Delphi 5 在特定情况下会生成无效的汇编代码。我不明白一般情况下是什么情况。下面的示例会产生访问冲突,因为发生了非常奇怪的优化。对于记录或数组中的一个字节,Delphi 生成 push dword [...], pop ebx, mov .., bl 如果在此字节之后有数据(我们至少需要三个才能正确推送 dword),则可以正常工作,但失败如果数据不可访问。我在这里用 win32 Virtual* 函数模拟了严格的界限

特别是在 FeedBytesToClass 过程中访问的块的最后一个字节时发生错误。如果我尝试更改诸如使用数据数组而不是删除 actionFlag 变量的对象属性之类的东西,Delphi 会生成正确的汇编指令。

const
  BlockSize = 4096;

type
  TSomeClass = class
  private
    fBytes: PByteArray;
  public
    property Bytes: PByteArray read fBytes;
    constructor Create;
    destructor  Destroy;override;
  end;

constructor TSomeClass.Create;
begin
  inherited Create;
  GetMem(fBytes, BlockSize);
end;

destructor TSomeClass.Destroy;
begin
  FreeMem(fBytes);
  inherited;
end;

procedure FeedBytesToClass(SrcDataBytes: PByteArray; Count: integer);
var
  j: integer;
  Ofs: integer;
  actionFlag: boolean;
  AClass: TSomeClass;
begin
  AClass:=TSomeClass.Create;
  try
    actionFlag:=true;

    for j:=0 to Count-1 do
    begin
      Ofs:=j;
      if actionFlag then
      begin
        AClass.Bytes[Ofs]:=SrcDataBytes[j];
      end;
    end;
  finally
    AClass.Free;
  end;
end;

procedure TForm31.Button1Click(Sender: TObject);
var
  SrcDataBytes: PByteArray;
begin
  SrcDataBytes:=VirtualAlloc(Nil, BlockSize, MEM_COMMIT, PAGE_READWRITE);
  try
    if VirtualLock(SrcDataBytes, BlockSize) then
      try
        FeedBytesToClass(SrcDataBytes, BlockSize);
      finally
        VirtualUnLock(SrcDataBytes, BlockSize);
      end;
  finally
    VirtualFree(SrcDataBytes, MEM_DECOMMIT, BlockSize);
  end;
end;

最初,当我使用访问位图位的 RGB 数据时发生错误,但那里的代码太复杂,所以我将其缩小到这个片段。

所以问题是这里有什么特别的东西让Delphi产生push、pop、mov优化。我需要知道这一点,以便在一般情况下避免这种副作用。

4

3 回答 3

10

哎呀,真是痛苦的问题。常量 actionFlag 的存在(与 4 的倍数的常量计数相结合)触发了处理数据的 push/pop 样式。对于那些对实际汇编程序感兴趣的人,(在 cpu 视图不提供复制/粘贴的日子里手动输入):

AClass.Bytes[Ofs] := SrcDataBytes[j];
  mov exc,[ebp-$04]
  push dword ptr [ecx+eax]   <- ouch
  mov ecx,[ebp-$08]
  mov ecx,[exc+04]
  lea esi,[exc+esi]
  pop ecx
  mov [esi],cl
end;
  inc eax

它这样做了 4096 次。我查了一下,Delphi 6 没有这种行为。我认为我们可以放心地假设它在任何更高版本中也已修复。

作为一种解决方法,我建议简单地将 {$O-}/{$O+} 添加到该方法中。我不会过多地深入研究确切的取证,因为触发 Delphi 执行这种错误优化的条件似乎很少见,而且 Delphi 版本确实相当古老。

常量标志通常不会成为内部循环的一部分,我怀疑您的计数通常也是动态的。但是,您在生产代码中遇到了这个问题,所以它可能并不像看起来那么罕见。我只能说我从来没有遇到过,我们 90% 的生产代码都是在 Delphi 5 中编写的。也许这只是默认内存分配的安全性。

于 2009-08-18T18:55:01.723 回答
3

不知道究竟是什么问题,因为你的问题相当难以捉摸......但有几点意见:

  • 为什么要使用 GetMem 而不是New,尤其是对于 ByteArray 而言块大小太小
    我没有 D5 但在 D2007 中:
    PByteArray = ^TByteArray;
    TByteArray = 字节数组[0.. 32767 ];

  • 尝试使用FastMM4进行内存分配。更容易看到正在发生的事情并发现问题。

顺便说一句,在 D2007 中,编译器生成的 asm 与Paul-Jan 发布的不同:

Unit7.pas.67: AClass.Bytes[Ofs]:=SrcDataBytes[j];
        mov ebx,[ebp-$04]
        movzx ebx,[ebx+eax]
        push ebx
        mov ebx,[ebp-$08]
        mov ebx,[ebx+$04]
        lea esi,[ebx+esi]
        pop ebx
        mov [esi],bl
Unit7.pas.69: end;
        inc eax
于 2009-08-18T18:35:40.593 回答
3

升级。Delphi 5 于 10 年前发布。

于 2009-08-18T15:40:40.103 回答