【问题标题】:Is it safe to use Default() assignment to initialize record variables in Delphi?在 Delphi 中使用 Default() 赋值来初始化记录变量是否安全?
【发布时间】:2014-12-03 14:07:39
【问题描述】:

将 Default(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。

【问题讨论】:

    标签: delphi


    【解决方案1】:
    GetMem(pr, SizeOf(TMyRecord));
    pr^ := Default(TMyRecord);
    

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

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

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

    动态分配包含托管类型的记录时,您应该使用New。如果您只是必须在这种情况下使用GetMem,您需要负责确保记录中的托管成员在任何后续使用记录之前适当地初始化。

    所以在我看来,你给你的问题提供了错误的标题。而不是

    使用Default()赋值来初始化记录安全吗?

    问题应该有标题

    在初始化之前对记录执行任何操作是否安全?

    【讨论】:

    • 只是补充一下,有人可能会说,“分配内存归零的 AllocMem 怎么样”?但是,对于GetMemAllocMem,您使用FreeMem,它不会最终确定,而对于New,您使用Dispose,它会最终确定。
    • 感谢您富有洞察力的评论,但我真的很想特别询问有关使用 Default() 赋值来初始化记录的问题。对我来说,这似乎是个坏主意,因为在我看来,在某些情况下,它会有效地对未初始化的垃圾调用 Finalize。
    • 我猜你在那种情况下根本不理解我的回答。我想不出更好的方式来表达自己。您对我的回答的评论清楚地表明您对这些问题的理解是错误的。我不能强迫你改正。
    • 启发我的问题是,建议我使用默认分配作为一种“无需考虑”初始化函数的方法,该函数始终可以在记录上调用,而无需考虑里面的内容记录,什么是“托管类型”,当 Delphi 自动生成对 Initialize 的隐式调用时,以及 Initialize 甚至做了什么等等。那么为什么有人想要这样的东西呢?例如,帮助某些具有 C 背景的程序员避免学习所有这些东西。毕竟,它只是一个结构,对吧? (不,不是)
    • 您已接受另一个答案。你告诉我我没有回答你的问题。我不觉得特别想和你争论。我知道我自己的意见。我没有热情与你争论并试图改变你。如果你想听我的建议,我很乐意给你。但在我看来,你已经明确表示你不同意我的观点,不想听我的意见。没关系。
    【解决方案2】:

    那么在这种情况下,使用默认赋值来初始化记录会不会不安全?

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

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

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

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

    换句话说,该代码并非旨在执行您想做的事情。这不会使代码有任何缺陷。它擅长 的设计目标。

    【讨论】:

      猜你喜欢
      • 2018-07-20
      • 2011-01-26
      • 2010-09-13
      • 1970-01-01
      • 2019-07-26
      • 1970-01-01
      • 2012-08-19
      • 1970-01-01
      相关资源
      最近更新 更多