Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi procedures with string parameters

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.
like image 317
Alexander Avatar asked Mar 10 '16 20:03

Alexander


1 Answers

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;
like image 101
Remy Lebeau Avatar answered Sep 26 '22 10:09

Remy Lebeau