我有这个代码,有一个使用重载和默认参数的过程:

program Project2;
{$APPTYPE CONSOLE}
uses SysUtils;

procedure Foo; overload; // not actually needed to reproduce
begin
end;

procedure Foo(const a: array of string; b: Boolean=False); overload;
begin
  Writeln(Length(a));
end;

begin
  Foo(['1', '2', '3']); // => 1 ???
  Foo(['1', '2', '3'], False); // => 3 OK
  Readln;
end.

输出是:
1
3

请注意,对 Foo 的第一次调用不提供默认值。为什么会这样?这个问题只与非常旧的编译器有关吗?

只有在使用 overload 键时才会发生这种情况。
procedure Foo2(const a: array of string; b: Boolean=False);
begin
  Writeln(Length(a));
end;

Foo2(['1', '2', '3']);

工作良好。

最佳答案

概括

正如您所发现的,David 已帮助澄清:这是 Delphi 5 中的一个错误(可能还有那个时代的其他几个版本)。在特定条件下,编译器无法正确调用该过程。

它本质上是两个功能的冲突:

  • 开放数组允许调用者将未指定长度的固定数组传递给过程。编译器在编译时确定长度并传递 附加隐藏 参数(High 索引),以便该方法可以正确确定数组中的元素数量。
  • 默认参数只是语法糖,允许调用者省略默认值。实现不受影响,但编译器会自动传递省略的参数,就像调用者传递了默认值一样。
  • 当过程被标记为过载时会发生该错误。编译器似乎“忘记”传递 隐藏的 High 索引 ,并在其位置传递默认值。


  • 解决方法

    我确定您已经在使用明显的解决方法,但为了完整起见,我将其包含在内。当我以前在 Delphi 5 中工作时,我们将 array of String 和 default 的所有组合替换为以下内容;
    (无论我们是否已经在使用 overload )。
    procedure Foo(const a: array of string; b: Boolean); overload; {Remove the default}
    begin
      ...
    end;
    procedure Foo(const a: array of string); overload;
    begin
      Foo(a, False); {And pass the default value via overload}
    end;
    

    细节

    您可以通过在 CPU 窗口中调试 (Ctrl + Alt + C) 并检查汇编代码来准确观察编译器如何无法正确调用 Foo

    您应该能够推断 Foo 过程被编译为预期:
  • eax 中开数组的地址
  • ecx
  • 中的第二个参数(默认)
  • High 中数组的 edx 索引

  • 注意我使用 Integer default 作为更独特的默认值。

    情况1
    procedure Foo(const a: array of string; b: Integer = 7);
    ...
    Foo(['a', 'b', 'c']);
    {The last few lines of assembler for the above call}
    lea eax,[ebp-$18] {Load effective address of array}
    mov ecx,$00000007 {Implicitly set default value 7}
    mov edx,$00000002 {The hidden High value of the open array}
    call Foo
    

    案例二
    procedure Foo(const a: array of string; b: Integer = 7); overload;
    ...
    Foo(['a', 'b', 'c']);
    
    lea eax,[ebp-$18]
    {The second parameter is now uninitialised!}
    mov edx,$00000007 {Instead the default is assigned to register for High(a)}
    call Foo
    

    案例3
    procedure Foo(const a: array of string; b: Integer = 7); overload;
    ...
    Foo(['a', 'b', 'c'], 5);
    
    lea eax,[ebp-$18]
    mov ecx,$00000005 {The explicit argument for 2nd parameter}
    mov edx,$00000002 {The hidden parameter is again correctly assigned}
    call Foo
    

    其他观察

    1) 正如上面的情况 2 所指出的,当错误出现时,ecx 未初始化。以下应证明效果:
    procedure Foo(const a: array of string; b: Integer = 2); overload;
    var
      I: Integer;
    begin
      for I := Low(a) to High(a) do Write(a[I]);
      Writeln(b);
    end;
    ...
    Foo(['a', 'b', 'c'], 23); {Will write abc23}
    Foo(['a', 'b', 'c']); {Will write abc, but the number probably won't be 2}
    

    2)该错误不会在动态数组中表现出来。动态数组的长度是其内部结构的一部分,因此不能忘记。

    关于delphi - 使用带有默认参数和重载过程的字符串数组,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47980067/

    10-11 22:59
    查看更多