使用Default()赋值在Delphi中初始化记录变量是否安全?

时间:2014-12-03 14:07:40

标签: delphi

将默认值(TMyRecord)分配给TMyRecord的变量称为内部调用Finalize,然后将内存清零,就像FillChar一样。例如,在以下问题的答案中已经说过,我确实测试过分配Default()确实会导致调用例如System._FinalizeRecord

How to properly free records that contain various types in Delphi at once?

Difference between Initialize(), Default() and FillChar()

我的问题是,初始化这样的记录是否总是安全的,即使在Delphi没有自动调用Initialize的情况下也是如此?对我来说,在未初始化的记录变量上调用Finalize似乎没有意义。在初始化之前,必须假定内存包含随机垃圾。在这种情况下,我对托管类型特别感兴趣,这些托管类型是动态分配内存的指针,Finalize例程应该通过减少它们的引用计数来完成,等等。在许多情况下,Delphi会自动生成对Initialize的调用,以确保其托管类型保持可管理状态。但并非总是如此。

这是一个示例,说明了一个有问题的案例。正如下面的答案所述,你不应该使用GetMem来分配包含这样的托管类型的记录,但是让我们假设某人做了,然后尝试使用Default()赋值作为初始化

type
  TMyRecord = record
    s1, s2, s3 : String;
  end;
  PMyRecord = ^TMyRecord;

var
  pr : PMyRecord;

begin
  GetMem(pr, SizeOf(TMyRecord)); 
  pr^ := Default(TMyRecord);
...

我故意使用GetMem()而不是New(),因为据我所知,GetMem()返回的内存不应该自动归零,并且编译器不应该自动调用Initialize。那么在这种情况下,为初始化记录使用默认分配不是不安全吗?

在大卫接受的答案中,他正在使用一种漂亮的Clear方法作为记录类型 How to properly free records that contain various types in Delphi at once? 让我们添加一个

  TMyRecord = record
    s1, s2, s3 : String;
    procedure Clear;
  end;
...
procedure TMyRecord.Clear;
begin
  Self := Default(TMyRecord);
end;

现在,Clear例程应该完全无法知道记录是否位于堆栈或堆上,并且已经调用了Initialize,而不是。

2 个答案:

答案 0 :(得分:7)

GetMem(pr, SizeOf(TMyRecord));
pr^ := Default(TMyRecord);

以上代码不正确。但这与Default()的使用无关。请考虑以下代码:

GetMem(pr, SizeOf(TMyRecord));
pr^ := ...;

无论您将...替换为什么,此代码都是错误的。换句话说,代码的问题不在于使用Default()。问题是使用GetMem。调用GetMem后,新分配的内存的内容定义不明确。执行分配时,第一步是完成记录的当前内容。由于这些内容不明确,任何事情都可能发生。

当动态分配包含托管类型的记录时,您应该使用New。如果您只是必须在此方案中使用GetMem,则需要负责确保在随后使用记录之前对记录中的管理成员进行适当初始化。

因此,在我看来,你提出了错误的标题。而不是

  

使用Default()分配来初始化记录是否安全?

这个问题应该标题为

  

在记录初始化之前对记录做任何事情是否安全?

答案 1 :(得分:3)

  

因此,在这种情况下,使用默认分配初始化记录是不安全的?

是的,这是不安全的。这将最终确定垃圾,这可能很容易崩溃,或更糟。

如果您需要初始化内存,请使用Initialize方法,或者编写一些代表您隐式执行此操作的内容。

  

现在,Clear例程应该完全无法知道记录是否位于堆栈或堆上,并且已经调用了Initialize,而不是。

该代码已假设Initialize已被调用,并已将该责任转移给调用者。这对我来说非常有意义。必须处理未初始化内存的代码是例外,而不是常态。

换句话说,该代码并不是为了做你想做的事而设计的。这并没有以任何方式使代码有缺陷。它擅长 设计用来做什么。