ほとんどの Delphi サンプルがレコードの初期化に FillChar() を使用するのはなぜですか?
-
13-09-2019 - |
質問
ちょっと疑問に思ったのですが、なぜほとんどの 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、デフォルト値で定数を宣言する方が良い方法です。
解決
歴史的な理由、ほとんど。 FillCharは()ターボパスカルの日にさかのぼると、そのような目的のために使用されました。それは塗りつぶし言うしばらくので、名前は本当に誤った名称のビットですのシャアの()、それは本当に記入されたのバイトの()。その理由は、最後のパラメータが文字のまたはのバイトを取ることができるということです。だから、FillChar(フー、にSizeOf(フー)、#0)とFillChar(フー、にSizeOf(フー)、0)が同等です。混乱のもう一つのソースは、Delphi 2009のように、FillCharはまだ唯一のCharはWideChar型と同等であってもバイトを埋めることです。ほとんどの人が実際に文字データでメモリを埋めるか、単にいくつかの指定されたバイト値でメモリを初期化するためにそれを使用するFillCharを使用するかどうかを決定するためにFillCharのための最も一般的な用途を見ながら、我々はそれがその使用を支配し、後者の場合であることがわかりましたむしろ前者より。それによって、私たちはFillCharバイト中心を維持することを決定します。
フィールドが含まれてFillCharでレコードをクリアすると、適切なコンテキストで使用されていない場合は危険なことができ、「管理」タイプ(文字列、バリアント、インターフェイス、動的配列)のいずれかを使用して宣言ことは事実です。それはあなたが今までにをその範囲内のレコードに行う最初のものであるとして、あなたが与えた例では、しかし、限り、ローカルに宣言レコード変数の上FillCharを呼び出すために、実際に安全です。その理由は、コンパイラがレコード内の文字列フィールドを初期化するコードを生成していることです。これは、すでに0(ゼロ)に文字列フィールドを設定しています。 の値が割り当てられた後だけレコード変数のにFillCharを使用して、すでに0である文字列フィールドを含む0バイト、と全体のレコードを上書きします(0、フー、にSizeOf(フー))FillCharの呼び出し文字列フィールドは、推奨されません。コンパイラは、既存のレコードの値が適切に割り当て中に確定されることを保証するために適切なコードを生成することができますので、あなたの初期化に一定の技法を使用すると、この問題は非常に良い解決策です。
他のヒント
あなたは後でDelphi 2009に持っている場合は、レコードを初期化するためにDefault
呼び出しを使用します。
Foo := Default(TFoo);
を参照してください。質問<のhref = "https://stackoverflow.com/q/11065821/にのデビッドの答え576719" >どのように適切に自由に一度、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
これは、いくつかの入力を保存し、フォーカスが重要なものに設定されます。
FillChar は、ゴミが入らないようにするのに適しています。 新しい、初期化されていない 構造 (レコード、バッファ、配列など)。
何をリセットするのかを知らずに、値を「リセット」するために使用しないでください。
ただ書くだけではダメ MyObject := nil
そしてメモリリークを回避することを期待しています。
特に、すべての管理型は注意深く監視する必要があります。
を参照してください。 ファイナライズ 関数。
記憶を直接いじれる力があるときは、自分の足を撃つ方法が常にあります。
フィルチャー 通常は埋めるために使用されます 配列 または 記録 数値型と配列のみを使用します。がある場合には慣れるべきではないというのは正しいです。 文字列 (または参照カウントされた変数) 記録.
を使用するというあなたの提案にもかかわらず、 定数 初期化すればうまくいきますが、あるときに問題が発生します。 可変長 配列 初期化したいのですが。
質問には次のような内容も含まれる可能性があります。
- なぜ フィルチャー
- そしてそうではありません ゼロメモリ?
ありません ゼロメモリ Windows の機能。ヘッダー ファイル (winbase.h
) これは、C の世界では、方向転換して memset を呼び出すマクロです。
memset(Destination, 0, Length);
ZeroMemory は、言語に依存しない用語です。 「メモリをゼロにするために使用できるプラットフォームの機能」
Delphi に相当するもの memset
は FillChar
.
Delphi にはマクロがないので(そしてインライン化の時代以前)、 ゼロメモリ つまり、実際に実行する前に、追加の関数呼び出しによるペナルティを負わなければならなかったのです。 フィルチャー.
いろいろな意味で、 フィルチャー パフォーマンスの微細な最適化ですが、現在は存在しません。 ゼロメモリ インライン化されています:
procedure ZeroMemory(Destination: Pointer; Length: NativeUInt); inline;
おまけの読み物
Windows には、 セキュアゼロメモリ 関数。とまったく同じことを行います ゼロメモリ. 。それと同じことをするなら ゼロメモリ, 、なぜ存在するのでしょうか?
一部のスマート C/C++ コンパイラは、メモリを次のように設定すると認識する可能性があるためです。 0
メモリを削除する前に、それは時間の無駄です - そして、への呼び出しを最適化して取り除きます。 ゼロメモリ.
Delphi のコンパイラが他の多くのコンパイラほど賢いとは思えません。だから必要はない SecureFillChar.
伝統的に、文字はシングルバイト(もはや真のDelphi 2009)ですので、#0とfillchar使用すると、それだけでNULLを含むように割り当てられたメモリ、またはバイト0、またはビン00000000
あなたは代わりに古いfillcharと同じ呼び出しのパラメータを持つ互換性のための ZeroMemory の機能を使用する必要があります。
この質問は年齢のために私の心にあったより広範な意味合いを持っています。私も、レコードにFillCharを使用して育ちました。私たちは、多くの場合、(データ)のレコードに新しいフィールドを追加し、もちろんFillChar(録音、にSizeOf(REC)、#0)は、このような新しい分野の世話をするので、これはいいです。私たちは「それを正しく行う」場合、我々は、レコード自体と結果のコードが読みにくくているだけでなく、我々は新しい追加いけない場合、おそらく誤ってあってもよいそのうちのいくつかの種類を列挙しているそのうちのいくつかは、レコードのすべてのフィールド、を反復処理する必要がありますレコードは熱心にそれにフィールド。文字列フィールドは、このようにFillCharは、今は、何もありません、一般的ではありません。数ヶ月前、私は周りに行って、反復クリアに文字列フィールドを持つレコード上のすべての私のFillCharsを変換し、単にタイプ(序/上の「塗りつぶし」を行うためのきちんとした方法がある場合、私は解決策と不思議と幸せではなかったですフロート)および変異体および文字列に「ファイナライズ」?
ここでFillCharを使用せずにものを初期化するためのより良い方法があります: