使用带有默认参数和重载过程的字符串数组
Using array of string with default parameter and overload procedure
我有这段代码,其中包含一个使用重载和默认参数的过程:
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']);
工作正常。
这显然是 Delphi-5 中的错误。只有满足以下条件才会发生:
程序标有overload
array of string
是第一个参数
没有向过程传递默认参数
总结
正如您所发现并且 David 帮助澄清的那样:这是 Delphi 5(可能还有那个时代的其他几个版本)中的错误。在特定条件下,编译器无法正确调用该过程。
它本质上是两个特征的冲突:
- 开放式数组允许调用方将未指定长度的固定数组传递到过程中。编译器在编译时确定长度并传递一个 附加隐藏 参数(
High
索引),因此该方法可以正确确定数组中元素的数量。
- 默认参数只是语法糖,允许调用者省略默认值。实现不受影响,但编译器会自动传递省略的参数,就好像调用者传递了默认值一样。
- 当过程被标记为重载时,就会出现错误。编译器看似 "forgets" 传递了 隐藏的
High
索引 ,并在其位置传递了默认值。
解决方法
我确定您已经在使用明显的解决方法,但为了完整起见,我将其包括在内。以前在Delphi5工作的时候,我们把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 window (Ctrl + Alt + C) 并检查汇编代码。
您应该能够推断出 Foo
过程被编译为期望:
eax
中打开数组的地址
ecx
中的第二个参数(默认值)
edx
中数组的High
索引
注意我使用了 Integer
default 作为一个更独特的默认值。
案例一
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-] {Load effective address of array}
mov ecx,[=11=]000007 {Implicitly set default value 7}
mov edx,[=11=]000002 {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-]
{The second parameter is now uninitialised!}
mov edx,[=12=]000007 {Instead the default is assigned to register for High(a)}
call Foo
案例三
procedure Foo(const a: array of string; b: Integer = 7); overload;
...
Foo(['a', 'b', 'c'], 5);
lea eax,[ebp-]
mov ecx,[=13=]000005 {The explicit argument for 2nd parameter}
mov edx,[=13=]000002 {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) 该错误不会出现在动态数组中。动态数组的长度是其内部结构的一部分,因此不能忘记。
我有这段代码,其中包含一个使用重载和默认参数的过程:
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']);
工作正常。
这显然是 Delphi-5 中的错误。只有满足以下条件才会发生:
程序标有
overload
array of string
是第一个参数没有向过程传递默认参数
总结
正如您所发现并且 David 帮助澄清的那样:这是 Delphi 5(可能还有那个时代的其他几个版本)中的错误。在特定条件下,编译器无法正确调用该过程。
它本质上是两个特征的冲突:
- 开放式数组允许调用方将未指定长度的固定数组传递到过程中。编译器在编译时确定长度并传递一个 附加隐藏 参数(
High
索引),因此该方法可以正确确定数组中元素的数量。 - 默认参数只是语法糖,允许调用者省略默认值。实现不受影响,但编译器会自动传递省略的参数,就好像调用者传递了默认值一样。
- 当过程被标记为重载时,就会出现错误。编译器看似 "forgets" 传递了 隐藏的
High
索引 ,并在其位置传递了默认值。
解决方法
我确定您已经在使用明显的解决方法,但为了完整起见,我将其包括在内。以前在Delphi5工作的时候,我们把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 window (Ctrl + Alt + C) 并检查汇编代码。
您应该能够推断出 Foo
过程被编译为期望:
eax
中打开数组的地址ecx
中的第二个参数(默认值)
edx
中数组的High
索引
注意我使用了 Integer
default 作为一个更独特的默认值。
案例一
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-] {Load effective address of array}
mov ecx,[=11=]000007 {Implicitly set default value 7}
mov edx,[=11=]000002 {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-]
{The second parameter is now uninitialised!}
mov edx,[=12=]000007 {Instead the default is assigned to register for High(a)}
call Foo
案例三
procedure Foo(const a: array of string; b: Integer = 7); overload;
...
Foo(['a', 'b', 'c'], 5);
lea eax,[ebp-]
mov ecx,[=13=]000005 {The explicit argument for 2nd parameter}
mov edx,[=13=]000002 {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) 该错误不会出现在动态数组中。动态数组的长度是其内部结构的一部分,因此不能忘记。