Summary
As you have discovered and David has helped clarify: this is a bug in Delphi 5 (and possibly a few other versions of that era). Under specific conditions the compiler fails to call the procedure correctly.
It's essentially a clashing of 2 features:
- An open array allows callers to pass a fixed array of unspecified length into a procedure. The compiler determines the length at compile-time and passes an additional hidden parameter (the
High
index) so the method can correctly determine the number of elements in the array.
- A default parameter is simply syntactic sugar allowing the caller to omit defaults. The implementation is unaffected, but the compiler automatically passes omitted parameters as if the caller had passed the default.
- The bug occurs when the procedure is marked for overload. The compiler seemingly "forgets" to pass the hidden
High
index, and passes the default in its place.
Workaround
I'm sure you're already using the obvious workaround, but I include it for completeness. When I used to work in Delphi 5, we replaced all combinations of array of String
and default with the following;
(regardless of whether we were already using 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;
Details
You can observe exactly how the compiler fails to call Foo
correctly by debugging within the CPU window (Ctrl + Alt + C) and examining the assembler code.
You should be able to deduce that the Foo
procedure is compiled to expect:
- The address of the open array in
eax
- The second argument (the default) in
ecx
- The
High
index of the array in edx
Note I used Integer
default for a more distinctive default value.
Case 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
Case 2
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
Case 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
Additional Observations
1) As pointed out in case 2 above, when the bug manifests, ecx
is left uninitialised. The following should demonstrate the effect:
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) The bug doesn't manifest with dynamic arrays. The length of a dynamic array is a part of its internal structure and hence cannot be forgotten.
3, 3
on D7. – Cottage3 3
as well – LansingLength()
. How on earth did you figured that out??? – Herwick