为什么大多数Delphi示例使用FillChar()来初始化记录?

时间:2009-04-23 22:36:19

标签: delphi initialization record

我只是想知道为什么大多数Delphi示例使用FillChar()来初始化记录。

type
  TFoo = record
    i: Integer;
    s: string; // not safe in record, better use PChar instead
  end;

const
  EmptyFoo: TFoo = (i: 0; s: '');

procedure Test;
var
  Foo: TFoo;
  s2: string;
begin
  Foo := EmptyFoo; // initialize a record

  // Danger code starts
  FillChar(Foo, SizeOf(Foo), #0);
  s2 := Copy("Leak Test", 1, MaxInt); // The refcount of the string buffer = 1
  Foo.s = s2; // The refcount of s2 = 2
  FillChar(Foo, SizeOf(Foo), #0); // The refcount is expected to be 1, but it is still 2
end;
// After exiting the procedure, the string buffer still has 1 reference. This string buffer is regarded as a memory leak.

此处(http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html)是关于此主题的说明。 IMO,使用默认值声明常量是一种更好的方法。

8 个答案:

答案 0 :(得分:36)

历史原因,主要是。 FillChar()可追溯到Turbo Pascal天,并用于此类目的。这个名字实际上有点用词不当,因为虽然它说填充字符(),但实际上是填充字节()。原因是最后一个参数可以使用char 一个字节。所以FillChar(Foo,SizeOf(Foo),#0)和FillChar(Foo,SizeOf(Foo),0)是等价的。混淆的另一个原因是,从Delphi 2009开始,FillChar仍然只填充字节,即使Char等同于WideChar。在查看FillChar的最常见用途的同时,为了确定大多数人是否使用FillChar实际用字符数据填充内存,或者只是用它来用一些给定的字节值初始化内存,我们发现后一种情况主导了它的使用而不是前者。有了这个,我们决定保持FillChar以字节为中心。

使用FillChar清除包含使用“托管”类型之一声明的字段(字符串,变体,接口,动态数组)的记录确实是不安全的,如果不在适当的上下文中使用的话。但是,在您给出的示例中,只要是对该范围内的记录执行的第一件事,就可以安全地在本地声明的记录变量上调用FillChar。原因是编译器已生成代码以初始化记录中的字符串字段。这已经将字符串字段设置为0(nil)。调用FillChar(Foo,SizeOf(Foo),0)只会用0字节覆盖整个记录,包括已经为0的字符串字段。在记录变量之后使用FillChar 将值分配给不推荐使用字符串字段。使用初始化常量技术是解决此问题的一个非常好的解决方案,因为编译器可以生成正确的代码,以确保在分配期间正确完成现有记录值。

答案 1 :(得分:17)

如果您有Delphi 2009及更高版本,请使用Default调用来初始化记录。

Foo := Default(TFoo); 

请参阅问题David's answerHow to properly free records that contain various types in Delphi at once?

编辑:

使用Default(TSomeType)调用的好处是,记录在清除之前已完成。没有内存泄漏,也没有对FillChar或ZeroMem的明确危险的低级别调用。当记录很复杂,可能包含嵌套记录等时,就会消除犯错的风险。

初始化记录的方法可以更简单:

const EmptyFoo : TFoo = ();
...
Foo := EmptyFoo; // Initialize Foo

有时您希望参数具有非默认值,然后执行以下操作:

const PresetFoo : TFoo = (s : 'Non-Default'); // Only s has a non-default value

这将节省一些打字,重点放在重要的东西上。

答案 2 :(得分:9)

FillChar可以确保你没有在新的,未初始化的结构(记录,缓冲区,arrray ......)中得到任何垃圾。
它不应该用于“重置”值而不知道你正在重置什么 只需写MyObject := nil并期望避免内存泄漏 特别是要仔细观察所有管理类型 请参阅 Finalize 功能。

当你有能力直接摆弄记忆时,总会有一种方法可以用脚射击自己。

答案 3 :(得分:4)

FillChar 通常用于仅使用数字类型和数组填充数组记录。如果记录中有字符串(或任何重新计算的变量),则不应该使用它。

虽然你建议使用 const 来初始化它会有效,但当我有一个可变长度 数组时会出现问题我想初始化。

答案 4 :(得分:2)

传统上,字符是单个字节(对于Delphi 2009不再适用),因此使用fillchar和#0将初始化分配的内存,使其仅包含空值,或字节0或bin 00000000。

您应该使用 ZeroMemory 函数进行兼容,它具有与旧fillchar相同的调用参数。

答案 5 :(得分:2)

这个问题可能还会问:

Windows中没有 ZeroMemory 功能。在头文件(winbase.h)中,它是一个宏,在C语言世界中,它会转过来并调用memset:

memset(Destination, 0, Length);

ZeroMemory是“可用于零内存的平台功能”的语言中性术语。

等效于memset的Delphi是FillChar

由于Delphi没有宏(并且在进行内联之前),因此调用 ZeroMemory 意味着您必须在实际进入 FillChar < / strong>。

因此,在许多方面,调用 FillChar 是一种性能微优化-内联 ZeroMemory 后,该优化已不存在:

procedure ZeroMemory(Destination: Pointer; Length: NativeUInt); inline;

奖金阅读

Windows还包含SecureZeroMemory函数。它的作用与 ZeroMemory 完全相同。如果它与 ZeroMemory 相同,为什么会存在?

因为某些聪明的C / C ++编译器可能会认识到,在释放内存之前将内存设置为0是浪费时间-并优化对 ZeroMemory 的调用。

我不认为Delphi的编译器像其他许多编译器一样聪明。因此不需要 SecureFillChar

答案 6 :(得分:1)

这个问题具有更广泛的含义,这已经在我脑海中长久存在。我也是,使用FillChar进行记录。这很好,因为我们经常向(数据)记录添加新字段,当然FillChar(Rec,SizeOf(Rec),#0)负责处理这些新字段。如果我们'正确地做',我们必须遍历记录的所有字段,其中一些是枚举类型,其中一些可能是记录本身,如果我们不添加新的,结果代码可读性较差,也可能是错误的勤奋地记录字段。字符串字段很常见,因此FillChar现在是禁止使用的。几个月前,我四处转换并将所有我的FillChars转换为带有字符串字段的记录到迭代清算,但我对解决方案感到不满意,并想知道是否有一种简单的方法可以对简单类型进行“填充”(序数/浮点数和变量和字符串的'Finalize'?

答案 7 :(得分:0)

这是一种不使用FillChar初始化内容的更好方法:

Record in record (Cannot initialize)
How to initialize a static array?