I have this code, with a procedure that uses an overload and a default parameter:
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.
The output is:
1
3
Note that the first call to Foo
does not provide a default value. Why is this happening? is this issue only related to very old compilers?
This only happens if the overload
key is used.
procedure Foo2(const a: array of string; b: Boolean=False);
begin
Writeln(Length(a));
end;
Foo2(['1', '2', '3']);
Works fine.
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:
High
index) so the method can correctly determine the number of elements in the array.High
index, and passes the default in its place.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;
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:
eax
ecx
High
index of the array in edx
Note I used Integer
default for a more distinctive default value.
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
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) 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With