组合匿名和嵌套过程时代码错误

时间:2013-09-04 08:18:30

标签: delphi delphi-xe anonymous-methods

我认为Delphi代码存在一些意外的访问冲突,我认为这是正确的,但似乎是错误编译的。我可以把它减少到

procedure Run(Proc: TProc);
begin
  Proc;
end;

procedure Test;
begin
  Run(
    procedure
    var
      S: PChar;

      procedure Nested;
      begin
        Run(
          procedure
          begin
          end);
        S := 'Hello, world!';
      end;

    begin
      Run(
        procedure
        begin
          S := 'Hello';
        end);
      Nested;
      ShowMessage(S);
    end);
end;

对我而言,S := 'Hello, world!'存储在错误的位置。因此,会引发访问冲突,或者ShowMessage(S)显示“Hello”(有时,在释放用于实现匿名过程的对象时会引发访问冲突)。

我正在使用Delphi XE,安装了所有更新。

我怎么知道这会导致问题?我知道如何重写我的代码以避免匿名程序,但我很难弄清楚它们在哪些情况下会导致错误的代码,所以我不知道在哪里避免它们。

对我来说,知道这是否在Delphi的后续版本中得到修复会很有趣,但仅仅是有趣的,此时升级不是一个选项。

在QC上,我可以找到类似#91876的最新报告,但在Delphi XE中解决了这个问题。

更新

基于AlexSC的评论,略作修改:

...

      procedure Nested;
      begin
        Run(
          procedure
          begin
            S := S;
          end);
        S := 'Hello, world!';
      end;

...

确实有效。

生成的机器代码
S := 'Hello, world!';
失败的程序中的

ScratchForm.pas.44: S := 'Hello, world!';
004BD971 B89CD94B00       mov eax,$004bd99c
004BD976 894524           mov [ebp+$24],eax

而正确的版本是

ScratchForm.pas.45: S := 'Hello, world!';
004BD981 B8B0D94B00       mov eax,$004bd9b0
004BD986 8B5508           mov edx,[ebp+$08]
004BD989 8B52FC           mov edx,[edx-$04]
004BD98C 89420C           mov [edx+$0c],eax

失败程序中生成的代码没有看到S已被移动到编译器生成的类,[ebp+$24]如何访问嵌套方法的外部局部变量如何访问局部变量。

2 个答案:

答案 0 :(得分:1)

没有看到整个汇编程序代码(程序测试)并且只假设您发布的代码段,可能是在失败的代码段上只有一个指针被移动到正确版本的位置,也有一些数据移动了。

所以似乎S:= S或S:=''导致编译器通过它自己创建一个引用,甚至可以分配一些内存,这可以解释它为什么会起作用。

我还假设这就是在没有S:= S或S:=''的情况下发生访问冲突的原因,因为如果没有为字符串分配内存(记住你只声明了S:PChar)那么会引发访问冲突,因为访问了未分配的内存。

如果您只是声明S:String,则可能不会发生这种情况。

扩展评论后的补充:

PChar只是一个必须存在的数据结构指针。 PChar的另一个常见问题是声明局部变量然后将PChar传递给该变量到其他过程,因为一旦例程结束就会释放局部变量,但是PChar仍会指向它,然后它会引发访问违规行为一旦访问。

每个文档存在的唯一可能性是声明类似const S: PChar = 'Hello, world!'这样的内容,因为编译器可以解析相对的Adresse。但这仅适用于常量,而不适用于上面示例中的变量。如上例所示,需要为PChar指向的字符串文字分配存储,如S:String; P:PChar; S:='Hello, world!'; P:=PChar(S);或类似字符。

如果它仍然失败并声明了String或Integer,那么可能变量在某处消失或突然在proc中不再可见,但这将是另一个与已经解释的现有PChar问题无关的问题。 / p>

最终结论:

可以执行S:PChar; S:='Hello, world!',但编译器只需将其分配为本地或全局常量,如const S: PChar = 'Hello, world!'确实保存到可执行文件中,第二个S := 'Hello'然后创建另一个{也被保存到Executable等等 - 但是S只指向分配的最后一个,所有其他的仍然在可执行文件中但在不知道确切位置的情况下不再可访问,因为S仅指向分配给最后一个。

因此,根据SHello, world!的最后Hello点,取决于哪一个。在上面的示例中,我只能猜测哪一个是最后一个,谁知道也许编译器也只能猜测并且取决于优化和其他不可预测的因素S可能突然指向未分配的Mem而不是最后一个时间执行Showmessage(S),然后引发访问冲突。

答案 1 :(得分:0)

  

我怎么知道这会导致问题?

现在很难说清楚 如果我们知道Delphi XE2中修复的性质,我们会处于更好的位置 你所能做的就是不要使用匿名功能 Delphi有程序变量,因此对匿名函数的需求并不是那么可怕 见http://www.deltics.co.nz/blog/posts/48

  

我很有兴趣知道这是否在以后的Delphi版本中得到修复

根据@Sertac Akyuz的说法,这已在XE2中得到修复。

我个人不喜欢匿名方法,并且不得不禁止人们在我的Java项目中使用它们,因为我们的代码库中有相当大一部分是匿名的(事件处理程序)。
在极端适度的情况下使用我可以看到用例 但是在Delphi中我们有程序变量和嵌套程序......不是那么多。