Delphi带有字符串参数的过程

9
在使用Delphi的过程和字符串时,我遇到了一个问题。事实上,我期望看到输出字符串“1S2S3S4S5S6S”,但实际输出是“1234S5S6”。在调试过程中,它说S1、S2、S3和S6字符串变量未初始化(S1、S2、S3、S6是空字符串,S4和S5的值为'S')。有人能解释一下吗?以下是代码:
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.

4
注意:当你声明一个带有const的参数时,你告诉编译器在函数执行期间不应该期望参数会发生变化。你需要确保自己遵守这个承诺;编译器无法为你检查它。在本例中,你正在通过S7修改S,同时声称S2S5不会改变。 - Rob Kennedy
1个回答

17

您的S7参数被声明为out参数,因此当函数被调用时,编译器将把传递的变量设置为空字符串。您将同一个S变量用于所有参数,包括输出参数,因此在函数内部使用参数值之前,S的值会从内存中擦除。

进一步解释一下,该过程使用register调用约定,其中S1..S3通过CPU寄存器(EAX、EDX和ECX)传递,而S4..S6则传递到堆栈上。输入的string变量在当前值被推送到S4S5的堆栈后被清空(S3S6只是指向变量的指针),并且在将该值分配给S1S2之前。因此,S1S2最终为nil,S4S5包含对擦除前原始'S'数据的指针,S3S6指向被擦除的string变量。

调试器可以显示所有这些操作。如果在调用MyProcedure()的行上设置断点,然后打开CPU视图,您将看到以下汇编指令:

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

为解决此问题,您需要使用不同的变量来接收输出:

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

或者将该过程更改为一个函数,通过其 Result 返回一个新的 String,而不是使用 out 参数:

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;

谢谢你的建议,但我仍然不明白为什么S4和S5有值“S”,而其他的没有。出了什么问题? - Alexander
好的,我现在明白了 :) 最后一个问题,为什么会按照这样的顺序发生呢?我的意思是,S4、S5 的值首先被推入堆栈,然后 S1、S2、S3 被推入寄存器。 - Alexander
5
如果寄存器参数先被存储到寄存器中,@Alexander,那么这些寄存器将无法供编译器计算堆栈参数的值使用。编译器知道它可以按任意顺序计算参数值,因此它选择一种对编译器方便的顺序。 - Rob Kennedy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接