为什么对象不默认为nil?

8
在Delphi中,继承自TObject的变量文档化的默认值是nil。然而,我遇到了一种情况,这种情况不是这样的。 通过IDE(F9)运行以下代码示例会得到混合结果。
var
  objTemp : TMemDataSet;
begin
  if (objTemp = nil) then
     ShowMessage('Nil');
end;
  • 32位/调试模式,默认值不是nil
  • 32位/发布模式,默认值不是nil
  • 64位/调试模式,默认值为nil
  • 64位/发布模式,默认值不是nil

我的理解是该值应始终默认为nil。

在XE2和XE5下进行了相同的测试,结果相同。

这是Delphi中的预期行为吗?


8
你的理解是错误的。除了托管类型(比如字符串),局部变量不会初始化。对象不是托管类型。请提供一份引用文献,证明“已记录的行为”有所不同。 - Ken White
有关XE5文档,请参见此处,在“声明变量”部分末尾。 - Ken White
明白了,谢谢。只是想澄清一下我的文档测试中的64位调试构建。由于编译器的一个巧合,它总是将本地TObject变量默认为nil? - Jeff Cope
可能与调试代码的生成方式有关,但这并不重要。由于本地变量默认情况下未初始化,因此在使用它们之前,您应始终确保它们已初始化。这是不可避免的;您可以自己进行显式赋值(MyObj := TSomething.Create;),或者分配其他地方创建的内容(MyObj := SomeParamMyObj := SomeFunctionResult)。不必要的初始化不会有任何影响(例如,MyString := '' 不会有任何影响,即使字符串是托管类型并已初始化为 '')。 - Ken White
3
记录一下,我没有给你的问题投反对票。 - Ken White
显示剩余7条评论
2个回答

12
您的理解有误。对于非托管类型(即非引用计数类型)的局部变量,它们不会被初始化。在使用它们之前,您必须先为它们赋值。
根据XE5 documentation(请参阅“声明变量”部分的底部 - 我在 Wiin32 中包含了类型错误,但强调是我的):
如果您没有明确初始化全局变量,则编译器将其初始化为0。对象实例数据(字段)也将初始化为0。在Win32平台上,本地变量的内容在分配值之前未定义。
请注意,每当Emba写“Win32”时,他们指的是非ARC编译器,因此上述内容也适用于Win64和OSX。
您可以在Delphi 2007中使用搜索术语“Variables”在帮助索引中找到相同的信息;它位于“variables VBScript”和“variables [OpenGL]”之间。
您看到的Win64调试构建的差异可能只是编译器所做的事情,或者是幸运的巧合,或者完全是其他原因。但这并不重要。正如您所知道的,本地变量默认情况下未初始化,因此在使用它们之前,请确保在所有情况下都进行了初始化。这不是一个难以执行的规则;当您声明本地变量时,只需遵循这个规则即可。请注意,保留HTML标签。
var
  MyObj: TSomething;

你可以自己分配一个值,或者使用代码中其他地方接收到的值:

MyObj := TSomething.Create;   // Created yourself
MyObj := GetSomething();      // Function result
MyObj := Self.SomethingCollection[Self.SomethingCount - 1]; // Local ref

没有任何理由需要依赖于局部变量的初始化与否,因为可以在将外部引用分配给局部变量之前对外部引用进行测试,或在分配外部引用后对局部变量进行测试:

if SomethingIGot = nil then
  raise Exception.Create('Received a nil parameter');
MyObj := SomethingIGot;

// or

MyObj := SomethingIGot;
if not Assigned(MyObj) then
  raise Exception.Create('MyObj was assigned a nil value');

3
肯已经向您解释了如何,让我来解释一下为什么...
在Delphi中,从TObject派生的变量的文档化行为是默认值为nil。
您有些混淆了: 类(即TObject的后代)的成员变量初始化为0或nil。 但是,对象引用本身是否初始化取决于上下文。
至少从Delphi 2开始就是这种情况。
原因是为了加速优化:
局部变量寿命短 局部(全局)变量位于堆栈上。 该内存结构不断重用相同的内存。 将变量初始化为nil(或0)不会节省任何工作,因为您应该将变量实例化为有用的东西。
procedure Test;
var
  MyObject: TMyObject;
begin   
  MyObject:= TMyObject.Create;  
  .....

在过程开始之前将其初始化为nil显然没有任何意义,因为在将其设置为非nil值之前,您无法使用它。
由于局部变量在声明它们的地方附近使用,所以几乎没有混淆的风险。

当过程结束时,局部变量会超出范围。
这实际上意味着这些变量曾经存在的内存空间会在另一个过程中被重用。

对象可以存在很长时间
创建对象时,系统在堆上为其分配内存。
Delphi使用自己的内存管理器。 对于所有对象,您可以确保在调用TObject.Create后,对象的所有成员变量都设置为0(或nil)。
正如David指出的那样,如果Create(从TObject进一步下一步)失败,则允许系统安全地释放该实例。

这也是有道理的,否则您将不得不在编写每个构造函数时初始化大量变量;现在您只需要给非null成员赋值即可。

这在几个层面上都是有道理的。
类可以有几十个或几百个成员变量。
它可以防止错误。
它允许处理构造函数中的错误。


其中一些事实是错误的。默认情况下,Win32会将这些块初始化为零。不是这样的。Delphi使用自己的内存管理器,但他们决定几乎使用相同的约定。同样不是这样的。在TObject.InitInstance中,实例通过显式调用FillChar进行了零化。这是有道理的,否则你必须在编写每个构造函数时初始化大量变量。实际上,这样做是为了安全地在部分构造的实例上调用Free。Windows并没有对涉及的内存块进行零初始化。 - David Heffernan
谢谢 David,现在已经修复了。 - Johan
@DavidHeffernan,这是否意味着如果我有想要加速的代码,我可以使用记录/对象(而不是类),而不会产生fillchar成本? - Johan
1
堆分配的成本远高于填充成本。栈分配是免费的。 - David Heffernan

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