在Delphi中为分支声明块级变量

4
在 Delphi Prism 中,我们可以声明仅在特定情况下需要的变量。
例如:在 Prism 中
 If acondition then
     begin
        var a :Integer;
     end;
    a := 3; //this line will produce error. because a will be created only when the condition is true

在分支语句中,无法将'a'赋值为3。

如何在Delphi Win32中声明仅可在分支语句中使用的变量,以便在满足特定条件时创建并减少内存使用;

如果降低内存使用不是问题,我们会有哪些缺点(或没有)?


6
为什么?因为那就是规定。 - David Heffernan
2
@vibeeshanRC,“内存使用”不是问题,但我也想在我的win32 Delphi代码中拥有块级变量。至于为什么:也许是因为我们抱怨得不够多? - Cosmin Prund
1
如果你遇到了内存使用问题,这绝对不会解决它们。 - Lieven Keersmaekers
是的,内存使用不是问题。封装是问题所在。 - David Heffernan
3
@vibeeshanRC 我认为大多数 Delphi 程序员可能会像 C++、C# 等语言一样更喜欢局部变量作用域。但我们目前还无法实现。也许有一天会实现。你提到的性能问题有些混淆了问题,因为那不是真正的问题所在。 - David Heffernan
显示剩余5条评论
6个回答

8

在 Delphi 中不支持像 Java 中的 局部变量声明语句 概念,但您可以声明一个子过程:

procedure foo(const acondition: boolean);

  procedure subFoo;
  var
    a: integer;
  begin
    a := 3;
  end;

begin
  If acondition then
  begin
    subFoo;
  end;
end;

4
您是想说“太长”的意思是什么?如果“a”代表了一个内存使用量很大的变量,比如TVeryBigArray,那么为它增加额外开销是值得的吧?也许您应该像Lieven评论所说的那样查看代码的其他方面。 - Despatcher
2
我真的不知道你在这里想表达什么,你只是想打少一些字符吗?移动声明有什么帮助呢?如果已经太长了怎么办?抱歉,但Delphi/Pascal是一种有点啰嗦的语言,这正是它易于维护和阅读的原因。它可以做聪明的事情,只是不能做这个特别的花招。 - Despatcher
2
对于具有名称的子过程,给予+1,这可以提高代码的可读性和清晰度。我希望 Delphi 永远不要支持块作用域变量。 - Warren P
1
@Warrent 块级作用域变量非常棒。我想写for i: Integer in List do - David Heffernan
2
单次遍历和本地作用域没有问题。这样的功能将是可选的。我现在向你保证,我不会强制你使用这样的新功能。 - David Heffernan
显示剩余8条评论

8
你提出的问题前提是错误的。你认为在允许块级变量的语言中,程序会在控制进入或离开这些变量作用域时为这些变量分配和释放内存。例如,当 acondition 为真时,你认为程序会调整堆栈以为进入该块腾出 a 变量的空间。但你错了。
编译器会计算所有声明变量和临时变量所需的最大空间,然后在进入函数时保留该空间。分配该空间就像调整堆栈指针一样简单;所需的时间通常与保留的空间大小无关。总之,你的想法实际上不会节省任何空间。
块级变量真正的优势在于它们的作用域被限制在块中。
如果你确实需要某些变量只在代码的一个分支中有效,则将该分支拆分为一个单独的函数,并将变量放在那里。

如果你真的很关心,就把它变成内联函数。 - Мסž
但是它确实可以节省内存,例如,如果在同一作用域级别下有16个块(即没有一个在其他块内),每个块都有一个占用1 MB的本地定义,那么它只会分配1 MB的堆栈空间,而不是16 MB。当然,这只是一个构造性的例子,但它确实展示了通过使用作用域声明来节省内存是可能的。 - HeartWare

4

在Delphi中,没有办法将变量的范围限制在整个程序之下。对于单个整数变量来说,担心这个问题并没有意义...但是对于大型数据结构,您应该动态分配它,而不是静态分配,也就是说,不要使用

var integers: array[1..10000]of Integer;

使用

type TIntArray: array of Integer;
var integers: TIntArray;
If acondition then
 begin
    SetLength(integers, 10000);
    ...
 end;

1
@vibeeshanRC 是的,在函数开始时变量将被“创建”,但在动态数组(或任何其他动态变量)的情况下,只会创建指针(win32中为4个字节),元素的内存不会分配,直到调用 SetLength() - ain
1
如果数组可以安全地放在堆栈中,并且我知道它需要多大,那么出于性能原因,我总是更喜欢使用堆栈而不是堆。 - David Heffernan
@David Heffernan,OP担心的是内存消耗,而不是性能问题。 - ain
@ain 局部作用域并不一定会导致堆栈消耗减少。这取决于编译器。即使没有局部作用域,编译器也可以重复使用堆栈位置。好的编译器会这样做。编译器很容易分析代码并确定可以重复使用堆栈的位置。 - David Heffernan
@A.Bouchez 另一方面,仅仅因为一种语言支持方法内的本地作用域变量,并不能保证编译器会生成导致堆栈重用的代码。正如你所说,这只是些语法糖。 - David Heffernan
显示剩余4条评论

3
你可以使用(可怕的)with语句加上返回记录的函数来模拟块级变量。以下是一些示例代码,写在浏览器中:
type TIntegerA = record
  A: Integer;
end;

function varAInteger: TIntegerA;
begin
  Result.A := 0;
end;

// Code using this pseudo-local-variable
if Condition then
  with varAInteger do
  begin
    A := 7; // Works.
  end
else
  begin
    A := 3; // Error, the compiler doesn't know who A is
  end;

编辑以澄清这个命题

请注意,这种巫术并不能真正替代块级变量:即使它们可能像大多数其他本地变量一样分配在堆栈上,编译器也没有准备好将它们视为这样。它不会进行相同的优化:返回的记录将始终存储在实际的内存位置,而真正的本地变量可能与CPU寄存器相关联。编译器也不会让您在"for"语句中使用此类变量,这是一个大问题。


a := 3 明显行不通,因为赋值操作在 with 块之外,编译器不知道 a 是什么。至于您评论中的 Tinteger.a := 3,我不确定其含义,可能是打错了。 - Cosmin Prund
TIntegerA.A := 3 不应该通过编译,因为 TIntegerA 是一个类型名。 - Cosmin Prund
刚才我终于理解了这段代码,它真的很棘手;抱歉之前说 Tinteger.a := 3 可以工作。 - VibeeshanRC
-1 是因为,从生成的代码角度来看,在这里使用 with 没有任何意义。它会一次性在堆栈上保留每个 TIntegerA,就像一个本地变量一样。在这里,with 只是另一种声明本地 TIntegerA 变量的方式。你应该使用本地变量、堆(通过仅在堆栈上添加指针存储但添加一个隐藏的 try..finally 块的接口)或子过程,甚至更好的是私有方法和面向对象设计。 - Arnaud Bouchez
@Cosmin 是的,Delphi编译器将始终在堆栈上分配它,因为它是一个函数结果(除非函数声明为inline,这不是该情况)。即使对于这样的整数变量,它使用寄存器而不是堆栈。OP混淆了“在栈上声明”和“生成的asm代码将使用堆栈”。这取决于编译器做出决定。因此,它与声明局部变量不同,因为它将减少编译器优化的范围。因此,“记录函数结果”技巧不是一个好答案。 - Arnaud Bouchez
显示剩余2条评论

3
请注意,这只是“语法糖”。编译器可能会确保您不在内部范围之外使用变量,但这并不意味着它可以节省内存。无论变量是否被使用,该变量可能仍然在过程入口代码中分配在堆栈上。据我所知,大多数ABI在进入时初始化堆栈并在退出时清除堆栈。在函数执行期间以更复杂的方式操作堆栈,包括处理不同的执行路径,可能会导致性能下降。相比于单个指令来保留堆栈空间,您需要在代码中散布多个指令,并确保正确恢复堆栈,这样做可能会更加低效,尤其是由于异常而导致的堆栈展开可能会变得更加复杂。如果目的是通过更好的作用域处理编写“更好”的代码,以确保不会在错误的位置使用错误的变量,那么这可能很有用,但如果您需要它作为节省内存的一种方式,那么这可能不是正确的方法。

2

说了这么多,Delphi有一个非常有用的功能,可以实现远比使用本地变量更多的目的:

  function Something: Integer;
  begin 
    // don't want any too long local variables...
    If acondition then
      asm
        // now I have lots of 'local' variables available in the registers 
        mov EAX, @AnotherVariable  //you can use pascal local variables too!
        // do something with the number 3
        Add EAX, 3
        mov @Result, EAX
        jmp @next
 @AnotherVariable: dd 10
 @next:
      end;
    end;
  end; 

这个例子有点毫无意义... :))


有人错过了两个笑脸 :( 这部分是在开玩笑 - 或许有些人缺乏幽默感... - Despatcher
遗憾的是,本地汇编块即将消失。 - Toon Krijthe

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