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

发布于 2024-07-18 02:31:57 字数 955 浏览 6 评论 0原文

我只是想知道,为什么大多数 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/潜在内存泄漏-by-initializing.html)是我关于这个主题的注释。 IMO,声明一个具有默认值的常量是更好的方法。

I just wondered, why most Delphi examples use FillChar() to initialize records.

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.

Here (http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html) is my note on this topic. IMO, declare a constant with default value is a better way.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

嘿嘿嘿 2024-07-25 02:31:57

主要是历史原因。 FillChar() 可以追溯到 Turbo Pascal 时代并用于此类目的。 这个名字确实有点用词不当,因为虽然它说的是 FillChar(),但它实际上是 FillByte()。 原因是最后一个参数可以采用字符字节。 因此 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字符串字段,不推荐。 使用初始化常量技术是这个问题的一个很好的解决方案,因为编译器可以生成正确的代码以确保现有的记录值在赋值期间正确完成。

Historical reasons, mostly. FillChar() dates back to the Turbo Pascal days and was used for such purposes. The name is really a bit of a misnomer because while it says FillChar(), it is really FillByte(). The reason is that the last parameter can take a char or a byte. So FillChar(Foo, SizeOf(Foo), #0) and FillChar(Foo, SizeOf(Foo), 0) are equivalent. Another source of confusion is that as of Delphi 2009, FillChar still only fills bytes even though Char is equivalent to WideChar. While looking at the most common uses for FillChar in order to determine whether most folks use FillChar to actually fill memory with character data or just use it to initialize memory with some given byte value, we found that it was the latter case that dominated its use rather than the former. With that we decided to keep FillChar byte-centric.

It is true that clearing a record with FillChar that contains a field declared using one of the "managed" types (strings, Variant, Interface, dynamic arrays) can be unsafe if not used in the proper context. In the example you gave, however, it is actually safe to call FillChar on the locally declared record variable as long as it is the first thing you ever do to the record within that scope. The reason is that the compiler has generated code to initialize the string field in the record. This will have already set the string field to 0 (nil). Calling FillChar(Foo, SizeOf(Foo), 0) will just overwrite the whole record with 0 bytes, including the string field which is already 0. Using FillChar on the record variable after a value was assigned to the string field, is not recommended. Using your initialized constant technique is a very good solution this problem because the compiler can generate the proper code to ensure the existing record values are properly finalized during the assignment.

暮凉 2024-07-25 02:31:57

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

Foo := Default(TFoo); 

请参阅David 对问题的回答如何在Delphi中一次正确地释放包含多种类型的记录?

编辑:

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

您初始化记录的方法可以变得更加简单:

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

有时您希望参数具有非默认值,然后这样做:

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

这将节省一些输入,并将焦点设置在重要的内容上。

If you have Delphi 2009 and later, use the Default call to initialize a record.

Foo := Default(TFoo); 

See David's answer to the question How to properly free records that contain various types in Delphi at once?.

Edit:

The advantage of using the Default(TSomeType) call, is that the record is finalized before it is cleared. No memory leaks and no explicit dangerous low level call to FillChar or ZeroMem. When the records are complex, perhaps containing nested records etc, the risk of making mistakes is eliminated.

Your method to initialize the records can be made even simpler:

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

Sometimes you want a parameter to have a non-default value, then do like this:

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

This will save some typing and the focus is set on the important stuff.

旧人 2024-07-25 02:31:57

FillChar 可以确保您不会在新的、未初始化的结构(记录、缓冲区、数组...)中得到任何垃圾。
在不知道您要重置的内容的情况下,不应使用它来“重置”值。
只不过是编写 MyObject := nil 并期望避免内存泄漏。
特别是所有托管类型都要仔细观察。
请参阅完成函数。

当你有能力直接摆弄记忆时,总有一种搬起石头砸自己脚的方法。

FillChar is fine to make sure you don't get any garbage in a new, uninitialized structure (record, buffer, arrray...).
It should not be used to "reset" the values without knowing what your are resetting.
No more than just writing MyObject := nil and expecting to avoid a memory leak.
In particulart all managed types are to be watched carefully.
See the Finalize function.

When you have the power to fiddle directly with the memory, there is always a way to shoot yourself in the foot.

追风人 2024-07-25 02:31:57

FillChar通常用于仅用数字类型和数组填充数组记录。 您是对的,当记录中存在字符串(或任何引用计数变量)时,不应该使用它。

尽管您建议使用 const 来初始化它,但是当我想要一个可变长度 数组时,就会出现问题初始化。

FillChar is usually used to fill Arrays or records with only numeric types and array. You are correct that it shouldn't be used to when there are strings (or any ref-counted variables) in the record.

Although your suggestion of using a const to initialize it would work, an issue comes into play when I have a variable length array that I want to initialize.

安穩 2024-07-25 02:31:57

问题还可能是问:

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

memset(Destination, 0, Length);

ZeroMemory 是语言中性术语,表示“您的平台的函数可以是用于将内存归零”

Delphi 中相当于 memset 的是 FillChar

由于 Delphi 没有宏(在内联时代之前),调用 ZeroMemory 意味着在实际到达 FillChar 之前,您必须承受额外函数调用的惩罚。 。

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

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

Bonus Reading

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

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

我不认为 Delphi 的编译器像许多其他编译器一样智能; 因此不需要 SecureFillChar

The question may also be asking:

There is no ZeroMemory function in Windows. In the header files (winbase.h) it is a macro that, in the C world, turns around and calls memset:

memset(Destination, 0, Length);

ZeroMemory is the language neutral term for "your platform's function that can be used to zero memory"

The Delphi equivalent of memset is FillChar.

Since Delphi doesn't have macros (and before the days of inlining), calling ZeroMemory meant you had to suffer the penalty of an extra function call before you actually got to FillChar.

So in many ways, calling FillChar is a performance micro-optimization - which no longer exists now that ZeroMemory is inlined:

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

Bonus Reading

Windows also contains the SecureZeroMemory function. It does the exact same thing as ZeroMemory. If it does the same thing as ZeroMemory, why does it exist?

Because some smart C/C++ compilers might recognize that setting memory to 0 before getting rid of the memory is a waste of time - and optimize away the call to ZeroMemory.

I don't think Delphi's compiler is as smart as many other compilers; so there's no need for a SecureFillChar.

肥爪爪 2024-07-25 02:31:57

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

您应该使用 < Strong>ZeroMemory 函数用于兼容性,其调用参数与旧的 fillchar 相同。

Traditionally, a character is a single byte (no longer true for Delphi 2009), so using fillchar with a #0 would initalize the memory allocated so that it only contained nulls, or byte 0, or bin 00000000.

You should instead use the ZeroMemory function for compatibility, which has the same calling parameters as the old fillchar.

谜兔 2024-07-25 02:31:57

这个问题有着更广泛的含义,多年来一直萦绕在我的脑海中。 我也是从小就使用 FillChar 进行记录。 这很好,因为我们经常向(数据)记录添加新字段,当然 FillChar( Rec, SizeOf( Rec), #0 ) 会处理这些新字段。 如果我们“做得正确”,我们必须迭代记录的所有字段,其中一些是枚举类型,其中一些可能是记录本身,如果我们不添加新的,结果代码的可读性较差,也可能会出错勤奋地记录字段。 字符串字段很常见,因此 FillChar 现在是禁忌。 几个月前,我四处走动,将带有字符串字段的记录上的所有 FillChars 转换为迭代清除,但我对解决方案并不满意,想知道是否有一种简洁的方法可以在简单类型(序数/ float) 和变体和字符串上的“最终确定”?

This question has a broader implication that has been in my mind for ages. I too, was brought up on using FillChar for records. This is nice because we often add new fields to the (data) record and of course FillChar( Rec, SizeOf( Rec), #0 ) takes care of such new fields. If we 'do it properly', we have to iterate through all fields of the record, some of which are enumerated types, some of which may be records themselves and the resulting code is less readable as well be possibly erroneous if we dont add new record fields to it diligently. String fields are common, thus FillChar is a no-no now. A few months ago, I went around and converted all my FillChars on records with string fields to iterated clearing, but I was not happy with the solution and wonder if there is a neat way of doing the 'Fill' on simply types (ordinal / float) and 'Finalize' on variants and strings?

淡写薰衣草的香 2024-07-25 02:31:57

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

记录在记录中(无法初始化)
如何初始化静态数组?

Here is a better way to initialize stuff without using FillChar:

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文