Search code examples
delphidelphi-7

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.

Solution

  • 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;