Delphi变量默认会被初始化吗?

115

我是Delphi的新手,一直在运行一些测试,以查看对象变量和堆栈变量默认情况下初始化为什么:

TInstanceVariables = class
  fBoolean: boolean; // always starts off as false
  fInteger: integer; // always starts off as zero
  fObject: TObject; // always starts off as nil
end;

这是我在其他编程语言中习惯的行为,但我想知道在 Delphi 中是否可以依赖它?例如,我想知道它是否可能取决于编译器设置,或者在不同的计算机上工作方式不同。您是否可以仅仅依赖默认初始化值来创建对象,还是需要在构造函数中显式地设置所有实例变量?

至于堆栈(过程级)变量,我的测试显示未初始化的布尔值为 true,未初始化的整数为 2129993264,未初始化的对象只是无效指针(即非 nil)。我猜想通常情况下应该在访问之前总是要先给过程级变量设置初值,对吗?


4
两点提示:1. 记录未初始化。2. 引用计数变量始终初始化。!但是! 在返回字符串的函数中,'Result' 并没有像您期望的那样被初始化为空字符串。这是因为'Result'不是本地变量。因此,总是执行:Result: =''。 - Gabriel
10个回答

119

是的,这是已记录的行为:

  • 对象字段始终初始化为0、0.0、''、False、nil或适用的任何值。

  • 全局变量也始终初始化为0等;

  • 本地引用计数*变量始终初始化为nil或'';

  • 本地非引用计数*变量未初始化,因此您必须在使用之前分配一个值。

我记得Barry Kelly曾经写过"reference-counted"的定义,但现在无法找到它,所以暂时只能这样解释:

reference-counted == 它们自身是引用计数的,或直接或间接包含字段(对于记录)或元素(对于数组),例如:string,variant,interface 或包含这些类型的动态数组静态数组

注:

  • record本身不足以成为引用计数
  • 我还没有尝试过这个泛型

9
如果本地变量是托管类型(例如字符串、接口、动态数组或变体类型),则它们将被初始化为 ($0)。 - Francesca
2
如果这是“正确”的答案,我认为为了完整性,有人应该添加枚举和集合的初始化内容。 - Peter Turner
5
但是有一个例外!当您覆盖构造函数并且不调用继承的构造函数时,有可能会导致某些字段未初始化!(特别是在旧版本的 Delphi 中。)因为 TObject.Create 负责清零所有数据,不调用它可能会导致可能未知的数据。 - Wim ten Brink
20
@WimtenBrink 我觉得你错了。初始化不是在TObject.Create中完成的,这是一个空方法,而是在class function TObject.InitInstance(Instance: Pointer): TObject;中完成的,它在任何构造函数调用之前始终被调用,即使对于旧版本的Delphi也是如此。我认为你的评论是错误和令人困惑的。 - Arnaud Bouchez
9
请记住,在返回字符串的函数中,'Result'并没有像你期望的那样初始化为空字符串。这是因为'Result'不是局部变量。 - Gabriel
显示剩余3条评论

29

没有显式初始化程序的全局变量被分配在可执行文件的BSS(Block Started by Symbol)段中。它们实际上不占用EXE文件中的任何空间;BSS段是操作系统分配并清零的一种特殊段。在其他操作系统中,也有类似的机制。

你可以依赖于全局变量被初始化为零。


27

类字段默认为零,这一点已经记录在案,因此您可以信赖它。 本地堆栈变量未定义,除非是字符串或接口,否则将设置为零。


“Zero” 有点让我困惑 - 它是否意味着字符串为'',接口为nil? - MB.
4
是的,正是如此。在汇编语言层面上,nil 等同于 0,而在 Delphi 约定中,''(空字符串)等同于 nil。 - gabr
4
“除非是字符串或接口”并不是对现实的完整描述。例如,动态数组也会被初始化。更普遍地说,如果是托管类型(引用计数类型)的变量,即使是局部变量,也会被初始化。 - Andreas Rejbrand
1
不要将本地堆栈变量Result混淆,参见https://dev59.com/aG435IYBdhLWcg3wlBCC - Wolf

19

作为一个旁注(因为您是Delphi的新手):全局变量可以在声明它们时直接初始化:

var myGlobal:integer=99;

3
自10.3版本开始,本地变量同样适用此规则。 - Edijs Kolesnikovičs
2
如果没有明确地初始化,它们将被初始化为0、0.0、False、nil、[]等。 - Andreas Rejbrand
1
@EdijsKolesnikovičs:使用什么语法(初始化本地变量)?_VAR X: INTEGER = 0;_不起作用...(“错误E2195无法初始化本地变量”) - HeartWare

9

全局变量和对象实例数据(字段)总是初始化为零。 在Win32 Delphi中,过程和方法中的局部变量不会被初始化;它们的内容在代码中赋值之前是未定义的。


9
这是来自 Ray Lischners 的《Delphi in a Nutshell》的引用 第2章 “当 Delphi 首次创建对象时,所有字段都为空,即指针初始化为 nil,字符串和动态数组为空,数字的值为零,布尔字段为 False,变量设置为 Unassigned。(有关详细信息,请参见第 5 章中的 NewInstance 和 InitInstance。)”
确实需要对局部范围内的变量进行初始化……我认为上面的评论“全局变量被初始化”是可疑的,直到提供参考为止 - 我不相信那个。
编辑... Barry Kelly 说你可以依赖它们被零初始化,因为他在 Delphi 编译器团队中,我相信这是正确的 :) 谢谢 Barry。

3
在Delphi 2006的帮助文档中可以找到此信息:ms-help://borland.bds4/bds4ref/html/Variables.htm。"如果您没有显式初始化全局变量,编译器会将其初始化为0。对象实例数据(字段)也会被初始化为0。" - Giacomo Degli Esposti
2
因为“我不相信这个”的缘故被踩了。这是关于编程,而非宗教。Giacomo刚刚证明了事实。 - Gabriel

8

即使一种语言提供默认初始化,我也不认为您应该依赖它们。将其初始化为一个值可以让其他开发人员更加清楚,他们可能不知道语言中的默认初始化,并防止跨编译器出现问题。


6
当然可以,而且你应该这样做。在每个构造函数中将所有内容初始化为0/''/false/nil只是不必要的。另一方面,在全局变量中进行初始化则并不愚蠢——就我个人而言,我经常不记得它们是否被初始化(因为我没有经常使用它们)。 - gabr
4
如果Delphi允许您在声明变量的同时初始化变量(例如:var fObject: TObject = nil),我会倾向于同意将变量初始化为某个值可能是一个好主意。但对我来说,在每个对象字段的构造函数中都这样做似乎有些过头了。 - MB.

6

来自Delphi 2007帮助文件:

ms-help://borland.bds5/devcommon/variables_xml.html

"如果您没有显式初始化全局变量,编译器会将其初始化为0。"


6

我对给出的答案有一个小小的抱怨。Delphi会将全局变量和新创建的对象的内存空间清零。虽然这通常意味着它们已经初始化了,但是在特定情况下却不是这样的:具有特定值的枚举类型。如果零不是合法值呢?


1
零始终是一个合法的值,它是枚举的第一个值。你可以通过 ord(MyFirstEnumValue) 来查看它。 - Francesca
它将返回枚举类型中的第一个值。 - skamradt
8
如果你给枚举类型明确赋值,那么0并不总是合法的值。在这种情况下,它仍然被初始化为0,此时你就有了一个非法值。但枚举只是对普通整数类型进行语法上的封装,因此这并不会破坏任何东西。确保你的代码能够处理它。 - Mason Wheeler
2
@François:如果你像这样定义枚举:TOneTwoThree = (One=1, Two=2, Three=3);,就不会出现这种情况。 - fnkr

2
自 Delphi 10.3 开始引入的内联变量使得控制初始值更加容易。
procedure TestInlineVariable;
begin
  var index: Integer := 345;
  ShowMessage(index.ToString);
end;

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