Delphi procedures with string parameters
Asked Answered
B

1

9

I faced a problem while working with procedures and strings in Delphi. The fact is that I expected to see the output string "1S2S3S4S5S6S" but the actual output is "1234S5S6". During the debug process it says that S1, S2, S3 and S6 string variables are not initialized (S1, S2, S3, S6 are '' strings, S4 and S5 have value 'S'). Can someone explain to me this? Here's the code:

program StringTest;

{$APPTYPE CONSOLE}

procedure MyProcedure(S1: String; const S2: String; var S3: String;
                      S4: String; const S5: String; var S6: String;
                      out S7: String);
begin
  S7 := '1' + S1 + '2' + S2 + '3' + S3 + '4' + S4 + '5' + S5 + '6' + S6;
end;

procedure Work;
var
  S: String;
begin
  S := 'S';
  MyProcedure(S, S, S, S, S, S, S);
  writeln(S);
end;

begin
  Work;
  readln;
end.
Binny answered 10/3, 2016 at 20:30 Comment(1)
Beware: When you declare a parameter with const, you're telling the compiler that it should not expect the parameter to change for the duration of that function. It's your responsibility to ensure you uphold that promise; the compiler can't check it for you. In this case, you're modifying S through S7 while at the same time claiming S2 and S5 won't change.Musetta
H
17

Your S7 parameter is declared as an out parameter, so the compiler will set the passed variable to a blank string when the function is called. You are passing the same S variable for all of the parameters, including the output parameter, so the value of S gets wiped from memory before the parameter values are used inside the function.

To elaborate further, the procedure is using the register calling convention, where S1..S3 are passed in CPU registers (EAX, EDX, and ECX, respectively) and S4..S6 are passed on the stack instead. The input string variable is getting wiped clear after its current value is pushed on the stack for S4 and S5 (S3 and S6 are just pointers to the variable), and before the value is assigned to S1 and S2. So, S1 and S2 end up nil, S4 and S5 contain pointers to the original 'S' data before the wipe, and S3 and S6 are pointing at the string variable that was wiped.

The debugger can show you all of this in action. If you put a breakpoint at the line where MyProcedure() is called, and then open the CPU view, you will see the following assembly instructions:

StringTest.dpr.17: MyProcedure(S, S, S, S, S, S, S);
00405A6C 8B45FC           mov eax,[ebp-$04]  // [ebp-$04] is the current value of S
00405A6F 50               push eax           // <-- assign S4
00405A70 8B45FC           mov eax,[ebp-$04]
00405A73 50               push eax           // <-- assign S5
00405A74 8D45FC           lea eax,[ebp-$04]
00405A77 50               push eax           // <-- assign S6
00405A78 8D45FC           lea eax,[ebp-$04]
00405A7B E8B0EDFFFF       call @UStrClr      // <-- 'out' wipes out S!
00405A80 50               push eax           // <-- assign S7
00405A81 8D4DFC           lea ecx,[ebp-$04]  // <-- assign S3
00405A84 8B55FC           mov edx,[ebp-$04]  // <-- assign S2
00405A87 8B45FC           mov eax,[ebp-$04]  // <-- assign S1
00405A8A E8B9FEFFFF       call MyProcedure

To fix this, you need to use a different variable to receive the output:

procedure Work;
var
  S, Res: String;
begin
  S := 'S';
  Proc(S, S, S, S, S, S, Res);
  WriteLn(Res);
end;

Alternatively, change the procedure into a function that returns a new String via its Result instead of using an out parameter:

function MyFunction(S1: String; const S2: String; var S3: String;
                      S4: String; const S5: String; var S6: String): String;
begin
  Result := '1' + S1 + '2' + S2 + '3' + S3 + '4' + S4 + '5' + S5 + '6' + S6;
end;

procedure Work;
var
  S: String;
begin
  S := 'S';
  WriteLn(MyFunction(S, S, S, S, S, S));
end;
Hindustan answered 10/3, 2016 at 20:35 Comment(3)
Thanks for that advice, but I still don't understand why do S4 and S5 have value 'S' and the others don't. What's wrong?Binny
OK, i am getting it now :) Last question, why does this happen in such order? I mean the values of S4, S5 are pushed to stack first, then S1, S2, S3 to the registers.Binny
If the register parameters were stored to registers first, @Alexander, then those registers would be unavailable for the compiler to compute the values of the stack parameters. The compiler knows that it's allowed to compute parameter values in whatever order it chooses, so it chooses an order that's convenient to the compiler.Musetta

© 2022 - 2024 — McMap. All rights reserved.