为什么在过程中使用局部变量而不是 var 参数?

8

在Delphi中,SysUtils库提供了一个名为ScanBlanks的过程:

procedure ScanBlanks(const S: string; var Pos: Integer);
var
  I: Integer;
begin
  I := Pos;
  while (I <= Length(S)) and (S[I] = ' ') do Inc(I);
  Pos := I;
end;

我想知道为什么这个过程使用了I变量。我们不能直接使用Pos变量吗?

procedure ScanBlanks(const S: string; var Pos: Integer);
begin
  while (Pos <= Length(S)) and (S[Pos] = ' ') do Inc(Pos);
end;

是因为速度或内存的惩罚吗?有经验的人能解释一下原因/区别吗?

1
询问代码作者。我会不使用本地编写它。 - David Heffernan
6
翻译:猜测:局部变量可以提升到寄存器,但变量参数是内存引用,在循环中使用寄存器可能更快,然后再更新内存。 - Lasse V. Karlsen
@Lasse 那正是我猜测的。代码更长但更快。也许有人确定或者我会测试它。 - nfc1
2
使用本地变量的版本更快。同时将 Length(S) 移到循环外部可以提高性能。 - RM.
2个回答

3

除了性能方面可能会有所不同,在编写良好的代码中,这两个函数之间没有区别。

然而,在糟糕的代码中,它们之间存在巨大的差异。使用本地变量使得对函数的正确性推理变得更加容易。存在SPos在内存中重叠的可能性。你觉得如果Inc(Pos);修改字符串的内容或长度,该过程会如何行为呢?

使用本地变量可以轻松地看到精确会发生什么。我肯定会同意,让SPos重叠将是一种可怕的误用该过程,但该过程的行为应该与其规范相匹配,因此规范应该禁止这样的输入值,或者即使存在这样的输入值,该过程也应该按照规范进行操作。获得工作细节的精确细节很棘手。使该过程在所有情况下按规范工作很容易。


2

文档中说:

此外,避免将字符串索引作为变量参数传递,因为这会导致低效的代码。

但我认为这并不总是正确的。在一个足够简单的例子中,优化器会尽其所能。在我看来,这是一种微观优化,不应该在编写代码时困扰你。参见Jeff Atwood

这真的无关紧要!这真的无关紧要!

让我们看一个例子:

procedure StringIndexByVar(const S: string; var I: integer);
begin
  I := 1;
  while I <= Length(S) do
  begin
    Write(S[I]);
    inc(I);
  end
end;

procedure StringIndexByLocal(const S: string; var I: integer);
var
  LIndex: integer;
begin
  LIndex := 1;
  while LIndex <= Length(S) do
  begin
    Write(S[LIndex]);
    inc(LIndex);
  end;
  I := LIndex;
end;

这段代码在Win32下编译后为:
StringIndexByVar:
00417ACC 53               push ebx
00417ACD 56               push esi
00417ACE 8BDA             mov ebx,edx
00417AD0 8BF0             mov esi,eax
00417AD2 C70301000000     mov [ebx],$00000001
00417AD8 EB1D             jmp $00417af7
00417ADA 8B03             mov eax,[ebx]
00417ADC 0FB75446FE       movzx edx,[esi+eax*2-$02] <--- (1)
00417AE1 A18CC54100       mov eax,[$0041c58c]
00417AE6 E8E9D2FEFF       call @Write0WChar
00417AEB E824CCFEFF       call @Flush
00417AF0 E88FC6FEFF       call @_IOTest
00417AF5 FF03             inc dword ptr [ebx] <--- (2)
00417AF7 8BC6             mov eax,esi
00417AF9 E8C2F2FEFF       call @UStrLen
00417AFE 3B03             cmp eax,[ebx]
00417B00 7DD8             jnl $00417ada
00417B02 5E               pop esi
00417B03 5B               pop ebx
00417B04 C3               ret

StringIndexByLocal:
00417B08 53               push ebx
00417B09 56               push esi
00417B0A 57               push edi
00417B0B 8BFA             mov edi,edx
00417B0D 8BF0             mov esi,eax
00417B0F BB01000000       mov ebx,$00000001
00417B14 EB1A             jmp $00417b30
00417B16 A18CC54100       mov eax,[$0041c58c]
00417B1B 0FB7545EFE       movzx edx,[esi+ebx*2-$02]  <---(1)
00417B20 E8AFD2FEFF       call @Write0WChar
00417B25 E8EACBFEFF       call @Flush
00417B2A E855C6FEFF       call @_IOTest
00417B2F 43               inc ebx
00417B30 8BC6             mov eax,esi
00417B32 E889F2FEFF       call @UStrLen
00417B37 3BD8             cmp ebx,eax
00417B39 7EDB             jle $00417b16
00417B3B 891F             mov [edi],ebx
00417B3D 5F               pop edi
00417B3E 5E               pop esi
00417B3F 5B               pop ebx
00417B40 C3               ret

据我所理解,这里的字符串索引并没有区别,因为索引被加载到了ebx寄存器中(标有箭头1)。增加循环计数器(箭头2)需要进行内存访问,但这与字符串索引的计算无关。

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