Delphi中的变量初始值

6
我相信在Delphi中,本地整数变量不会被初始化为零。初始值是该内存位置上的任何内容。
因此,在下面的代码中,第一次单击按钮时,第一个消息显示一个整数值。
第二次点击它为什么不显示3,而是显示相同的整数值?每次单击按钮时,它都继续显示相同的整数值。只有当我停止并重新启动程序时,该值才会改变。
无论何时单击按钮,它似乎使用的是相同的内存位置,那么3存储在哪里?
procedure TForm1.Button1Click(Sender: TObject);

var
int1 : integer;

begin
   showmessage(inttostr(int1)) ;
   int1 := 3;
end;

end.

请查看此评论:https://dev59.com/i3VC5IYBdhLWcg3w-WOs - pritaeas
6个回答

12

kjack,

它包含的是在那个时刻栈帧里的任何值。在你的情况下,这将是“Sender”。如果你将整数转换为对象并进行类型转换,你会注意到这个“模式”。

procedure TForm1.Button1Click(Sender: TObject);

var
int1 : integer;

begin
   ShowMessage(TObject(int1).ClassName);
   showmessage(inttostr(int1)) ;
   int1 := 3;
end;

end.

不完全正确,int1和Sender没有在同一空间中定义。(虽然使用绝对指令可以实现这一点)。 - Toon Krijthe
是的,ShowMessage(TObject(int1).ClassName); 一直显示 TButton,所以我认为你是对的。不过我现在有点迷失了。 - kjack
1
不,Lieven,Sender并不是通过EAX传递的。它是通过EDX传递的。Self是通过EAX传递的。Int1可能来自堆栈,也可能来自寄存器;这是编译器的决定。而调用ClassName函数的行为可能会将生成的代码更改为其他内容。 - Rob Kennedy
@Rob,EDX,你们当然是对的,是我的错误。针对OP的原始问题和那段特定的代码,并且在每天的同一时间使用相同的编译器(你懂的:),我非常确定int1将始终被初始化为[ebp-4],即Self。 - Lieven Keersmaekers
很棒。我每天都学到新东西。但最好不要在我的代码中使用它! :) - Toby Allen
显示剩余3条评论

5

首先,你是正确的,局部变量没有被初始化

此外,你不能保证int1在每次调用时都存储在相同的内存位置。在这种情况下,你看到相同的值的原因可能是因为它确实使用了相同的位置(偶然),但是Delphi编译器已经优化掉了你的最终结果。

int1 := 3;

这个语句没有任何效果。(你可以在那行代码后面添加另一个 showmessage(inttostr(int1)) 调用,看看是否有所不同。)另一种可能是,int1 使用的内存位置在按钮处理程序的多次调用(例如在 Windows 消息循环中)之间被重复使用,并且恰好总是将其重置为您看到的值。


2
只有在两次单击Button1之间不执行其他代码,特别是使用与到达TForm1.Button1Click()过程相同数量(或更多)的堆栈空间的代码时,才会成立。除非您覆盖堆栈上的值,否则它们仍将包含相同的值。
您可以执行的操作是添加另一个带有OnClick处理程序的按钮,该处理程序调用具有比ShowMessage()参数更多的方法。如果您在两次单击Button1之间单击该按钮,则int1的值确实应更改。这是因为Button1Click()中int1的堆栈位置将用于另一个处理程序中的一个参数,并被覆盖。
赋值应该被优化掉,您可以看到该行不应该在gutter区域中具有蓝点。还应该有编译器提示。
编辑:正如您所评论的那样,当单击另一个按钮时似乎没有行为变化。从那里我只能假设VCL代码(在调用OnClick处理程序之前执行)使用了太多的堆栈空间,以至于int1的内存位置始终初始化为某些(稳定的)值。此代码始终执行相同的操作,因此,如果涉及对象(Application、父窗体和按钮)的地址不变,则值也将保持不变。另一方面,重新启动应用程序将产生一个新的、同样稳定的未初始化本地变量值。
请注意,所有这些都高度依赖于在处理程序之前执行的VCL代码,因此对VCL的更改也可能会改变它。甚至可能是各种Delphi版本已经表现不同。

我按照你的建议添加了第二个按钮,但点击按钮1时并没有改变行为。Lieven的答案似乎是正确的。 - kjack

1

本地变量位于堆栈帧中。它们没有被初始化。

如果您两次访问一个方法,则有可能堆栈指针相等,从而得到相同的值。

例如:

调用前的堆栈:

> More stack  (memory location X)

如果调用了 Button1Click,则堆栈就会变成这样:
> Int1
> Return address
> Sender
> Self pointer
> More stack  (memory location X)

如果下次调用Button1Click时,堆栈指针仍停留在位置X,并且没有其他函数改变了这些值,你会发现Int1的值是相同的。

如果你有安全信息,清除本地变量总是明智的(但你也有机会让优化器删除这些状态。所以你需要禁用优化)。

只是为了好玩,再添加一个按钮:

procedure TForm1.Button2Click(Sender: TObject);
var
  int1 : integer;
begin
  showmessage(inttostr(int1)) ;
  int1 := 777;
end;

请检查:

  • 点击1:垃圾
  • 再次点击1:3
  • 点击2:3
  • 点击1:777

我这样做,每次点击都得到了垃圾数据,并且两个按钮的垃圾数据都是相同的!只有当我停止并重新启动程序时,它才会改变。我认为垃圾数据实际上是指TButton,就像Lieven所说的那样。 - kjack
实际上,我再次运行它时,这两个按钮的垃圾值是不同的,但它们仍然都是垃圾。 - kjack
@kjack:这不是垃圾,而是指向被点击的按钮的剩余指针,源自最终调用您的OnClick处理程序的VCL代码。这就是为什么对于不同的被点击的按钮它是一个不同的值。 - mghie
1
Self和Sender不在堆栈中,它们在寄存器中。Int1也很可能在一个寄存器中。从IntToStr返回的临时位置将在堆栈上,但如果Int1在堆栈上,它们将在不同的位置。 - Rob Kennedy

1

你想让它保持不变吗?如果是这样,你可以使用本地常量:

procedure TForm1.Button1Click(Sender: TObject);
const
  i: integer = 0;
begin
  i := i + 1;
  edit1.text := intToStr(i);
end;

你必须确保“可分配类型常量”已经打开。


不,我认为你永远都不应该这样做。 - J...
“不,它不是这样工作的”,或者“不,即使它能工作,这也不是一个好主意,因为...”? - Vegar
我并没有说它不起作用,我只是建议除了学术代码之外的任何情况下都要强烈避免使用它。它是一个设计工具,其使用需要极为令人信服的论据来证明容忍它引入的许多明显的复杂性比其他许多可以轻松获得和理智的替代方案更有价值。 - J...
它到底引入了哪些“显而易见的复杂性”?它是否会泄漏内存?它是否会产生随机编译器故障?在软件中很难找到错误?我已经使用可分配类型常量编写了15年的“非学术”Delphi代码,而没有遇到这些明显的复杂性... - Vegar
我不打算进行辩论。如果你真的想要一个好的理由清单,请在主题上发布一个问题。否则,我认为这个评论线程中已经有足够的信息了。 - J...

0

变量的初始化可能由内存管理器完成,也可能不完成。

我认为任何内存管理器都应该将所有变量初始化为零(0x0000),这是一个好的实践。在 .Net 中也是如此。


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