web-dev-qa-db-ja.com

ほとんどのDelphiの例でFillChar()を使用してレコードを初期化するのはなぜですか?

ほとんどの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、デフォルト値で定数を宣言するのがより良い方法です。

28
stanleyxu2005

主に歴史的な理由。 FillChar()はTurbo Pascalの時代にさかのぼり、そのような目的で使用されました。この名前は、Fill Char()と表示されていますが、実際にはFill Byte()であるため、実際には少し誤称です。その理由は、最後のパラメーターがchar またはバイトを取ることができるためです。したがって、FillChar(Foo、SizeOf(Foo)、#0)とFillChar(Foo、SizeOf(Foo)、0)は同等です。もう1つの混乱の原因は、Delphi 2009の時点では、CharがWideCharと同等であっても、FillCharは依然としてバイトを埋めるだけであるということです。ほとんどの人がFillCharを使用して実際に文字データでメモリを埋めるか、それとも特定のバイト値でメモリを初期化するために使用するかを判断するために、FillCharの最も一般的な使用法を調べたところ、後者の場合がその使用を支配していることがわかりました。前者ではなく。それで、FillCharをバイト中心に保つことにしました。

「マネージド」タイプ(文字列、バリアント、インターフェイス、動的配列)のいずれかを使用して宣言されたフィールドを含むFillCharを使用してレコードをクリアすると、適切なコンテキストで使用されない場合、安全ではない可能性があります。ただし、指定した例では、ローカルで宣言されたレコード変数でFillCharを呼び出すことは、そのスコープ内のレコードに対して最初に行うことである限り、実際には安全です。その理由は、コンパイラがレコード内の文字列フィールドを初期化するコードを生成したためです。これにより、文字列フィールドはすでに0(nil)に設定されています。 FillChar(Foo、SizeOf(Foo)、0)を呼び出すと、すでに0になっている文字列フィールドを含むレコード全体が0バイトで上書きされます。レコード変数でFillCharを使用するとafter値が割り当てられました文字列フィールドはお勧めしません。コンパイラーは適切なコードを生成して、割り当て中に既存のレコード値が適切にファイナライズされるようにすることができるため、初期化された定数手法を使用することは、この問題の非常に優れた解決策です。

36
Allen Bauer

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
_

これにより、タイピングが節約され、重要なものに焦点が当てられます。

17
LU RD

FillCharは、new、uninitialized構造(record、buffer、arrray ...)にガベージが入らないようにするのに適しています。
何をリセットするかを知らずに値を「リセット」するために使用しないでください。
ただ書くだけですMyObject := nilそしてメモリリークを回避することを期待しています。
特に、すべての管理対象タイプは注意深く監視する必要があります。
Finalize関数を参照してください。

記憶を直接いじる力があるときは、常に自分の足を撃つ方法があります。

9
François

FillCharは通常、Arraysまたはrecordsを数値型と配列のみで埋めるために使用されます。 recordstrings(またはref-counted変数)がある場合は、これに慣れてはいけないというのは正しいことです。

constを使用して初期化するという提案は機能しますが、可変長-がある場合に問題が発生しますarray初期化したい。

4
Jim McKeeth

質問はまた尋ねているかもしれません:

WindowsにはZeroMemory関数はありません。ヘッダーファイル(winbase.h)では、Cの世界では、向きを変えてmemsetを呼び出すマクロです。

memset(Destination, 0, Length);

ZeroMemoryは、「メモリをゼロにするために使用できるプラットフォームの機能」の言語中立用語です。

memsetに相当するDelphiはFillCharです。

Delphiにはマクロがないため(インライン化の前)、ZeroMemoryを呼び出すと、実際に/に到達する前に追加の関数呼び出しのペナルティを受ける必要がありました。FillChar

したがって、多くの点で、FillCharの呼び出しは、パフォーマンスのマイクロ最適化です。これは、ZeroMemoryが存在するため存在しなくなりました。インライン化:

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

ボーナスリーディング

Windowsには、 SecureZeroMemory 関数も含まれています。ZeroMemoryとまったく同じことを行います。ZeroMemoryと同じことをするのなら、なぜそれが存在するのですか?

一部のスマートC/C++コンパイラは、メモリを削除する前にメモリを0に設定するのは時間の無駄であると認識し、ZeroMemoryへの呼び出しを最適化する可能性があるためです。 /。

Delphiのコンパイラは他の多くのコンパイラほど賢いとは思いません。したがって、SecureFillCharは必要ありません。

3
Ian Boyd

従来、文字は1バイト(Delphi 2009では当てはまりません)であるため、#0でfillcharを使用すると、割り当てられたメモリが初期化され、null、バイト0、またはビン00000000のみが含まれるようになります。

代わりに、互換性のためにZeroMemory関数を使用する必要があります。この関数には、古いfillcharと同じ呼び出しパラメーターがあります。

2
skamradt

この質問は、何年もの間私の頭の中にあったより広い意味を持っています。私も、FillCharをレコードに使用することで育ちました。 (データ)レコードに新しいフィールドを追加することがよくあり、もちろん、FillChar(Rec、SizeOf(Rec)、#0)がそのような新しいフィールドを処理するので、これは素晴らしいことです。 「適切に実行する」場合、レコードのすべてのフィールドを反復処理する必要があります。その一部は列挙型であり、一部はレコード自体である可能性があり、結果のコードは読みにくく、新しいものを追加しないとエラーになる可能性があります。それにフィールドを熱心に記録します。文字列フィールドは一般的であるため、FillCharはノーノーになりました。数か月前、文字列フィールドのあるレコードのすべてのFillCharを繰り返しクリアに変換しましたが、解決策に満足できず、単純な型(ordinal /)で「Fill」を実行するための適切な方法があるかどうか疑問に思いました。 float)およびバリアントと文字列の「ファイナライズ」?

1
Brian Frost

FillCharを使用せずにものを初期化するためのより良い方法は次のとおりです。

レコードに記録(初期化できません)
静的配列を初期化する方法は?

0
WeGoToMars