代码之家  ›  专栏  ›  技术社区  ›  stanleyxu2005

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

  •  26
  • stanleyxu2005  · 技术社区  · 15 年前

    我只是想知道,为什么大多数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 回复  |  直到 6 年前
        1
  •  35
  •   Thomas Ahle    15 年前

    主要是历史原因。fillchar()可以追溯到turbo-pascal时代,并用于此目的。这个名字确实有点用词不当,因为当它说fill时 烧焦 (),真的满了 字节 ()原因是最后一个参数可以采用char 一个字节。因此,fillchar(foo,sizeof(foo),0)和fillchar(foo,sizeof(foo),0)是等效的。另一个令人困惑的原因是,截至Delphi2009年,即使char相当于widechar,fillchar仍然只填充字节。在研究Fillchar最常见的用法以确定大多数人是使用Fillchar实际用字符数据填充内存,还是仅使用它用某个给定的字节值初始化内存时,我们发现后一种情况主导了它的使用,而不是前一种情况。因此,我们决定以fillchar字节为中心。

    如果不在适当的上下文中使用,则清除包含使用“托管”类型之一(字符串、变量、接口、动态数组)声明的字段的Fillchar记录是不安全的。然而,在您给出的示例中,对本地声明的记录变量调用fillchar实际上是安全的。 只要这是你在这个范围内对记录做的第一件事 . 原因是编译器已经生成代码来初始化记录中的字符串字段。这将已将字符串字段设置为0(零)。调用fillchar(foo,sizeof(foo),0)只会用0字节覆盖整个记录,包括已经为0的字符串字段。在记录变量上使用FILLCHAR 之后 不建议为字符串字段指定值。使用初始化常量技术是这个问题的一个很好的解决方案,因为编译器可以生成适当的代码,以确保在分配期间正确地完成现有的记录值。

        2
  •  15
  •   Community Bayu Bramantya    7 年前

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

    Foo := Default(TFoo); 
    

    David's answer 对这个问题 How 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
    

    这将节省一些输入,重点放在重要的内容上。

        3
  •  9
  •   Francesca    15 年前

    Fillchar可以确保在 新建,未初始化 结构(记录、缓冲区、数组…)。
    不应在不知道重置内容的情况下使用它来“重置”值。
    不仅仅是写作 MyObject := nil 希望避免内存泄漏。
    特别是,所有管理类型都要仔细观察。
    定稿 功能。

    当你有能力直接摆弄记忆时,总有一种方法可以让你的脚受伤。

        4
  •  4
  •   Jim McKeeth    15 年前

    菲尔查尔 通常用来填充 数组 记录 只有数字类型和数组。你说得对,如果有 (或任何引用计数变量) 记录 .

    尽管你建议使用 康斯特 要初始化它,当我有一个 可变长度 数组 我要初始化的。

        5
  •  2
  •   skamradt    15 年前

    传统上,字符是一个单字节(对于Delphi2009不再是这样),因此使用带0的fillchar将初始化分配的内存,使其只包含空值、字节0或bin 00000000。

    你应该使用 零内存 用于兼容性的函数,其调用参数与旧Fillchar相同。

        6
  •  2
  •   Ian Boyd    6 年前

    问题也可能是:

    没有 零内存 在Windows中运行。在头文件中( winbase.h )它是一个宏,在C世界中,它会反过来调用memset:

    memset(Destination, 0, Length);
    

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

    Delphi等价于 memset FillChar .

    因为Delphi没有宏(并且在内联之前),所以调用 零内存 意味着您必须在实际开始之前接受额外函数调用的惩罚。 菲尔查尔 .

    所以在很多方面, 菲尔查尔 是一个性能微优化-现在已经不存在了 零内存 内联:

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

    奖金读数

    Windows还包含 SecureZeroMemory 功能。它的作用和 零内存 . 如果它和 零内存 ,它为什么存在?

    因为一些智能C/C++编译器可能会认识到将内存设置为 0 在清除内存之前是浪费时间-并优化掉对 零内存 .

    我不认为Delphi的编译器和其他编译器一样聪明,所以不需要 安全保险柜 .

        7
  •  1
  •   Brian Frost    15 年前

    这个问题有一个更广泛的含义,在我的脑海里已经有好多年了。我也是,从小就用Fillchar做记录。这很好,因为我们经常在(数据)记录中添加新字段,当然Fillchar(rec,sizeof(rec),0)会处理这些新字段。如果我们“正确地做”,我们必须遍历记录的所有字段,其中一些字段是枚举类型,其中一些字段可能是记录本身,结果代码的可读性较低,如果我们不努力地向其添加新的记录字段,也可能是错误的。字符串字段很常见,因此fillchar现在是no。几个月前,我把字符串字段记录中的所有FillChar转换为迭代清除,但我对解决方案不满意,我想知道是否有一种简单类型(顺序/浮点)的“填充”和变量和字符串的“最终确定”的简洁方法?

        8
  •  0
  •   Community Bayu Bramantya    7 年前

    以下是不使用fillchar初始化资料的更好方法:

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