31 Stimmen

Warum verwenden die meisten Delphi-Beispiele FillChar(), um Datensätze zu initialisieren?

Ich habe mich gerade gefragt, warum die meisten Delphi-Beispiele FillChar() zum Initialisieren von Datensätzen verwenden.

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.

Hier ( http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html ) ist meine Anmerkung zu diesem Thema. IMO, deklarieren eine Konstante mit Standardwert ist ein besserer Weg.

38voto

Allen Bauer Punkte 16377

Hauptsächlich aus historischen Gründen. FillChar() stammt noch aus der Zeit von Turbo Pascal und wurde für solche Zwecke verwendet. Der Name ist eigentlich ein wenig irreführend, denn obwohl er Fill Char (), es ist wirklich Fill Byte (). Der Grund dafür ist, dass der letzte Parameter ein char annehmen kann ou ein Byte. FillChar(Foo, SizeOf(Foo), #0) und FillChar(Foo, SizeOf(Foo), 0) sind also gleichwertig. Eine weitere Quelle der Verwirrung ist, dass FillChar seit Delphi 2009 nur noch Bytes füllt, obwohl Char äquivalent zu WideChar ist. Als wir uns die häufigsten Verwendungen von FillChar ansahen, um herauszufinden, ob die meisten Leute FillChar verwenden, um den Speicher tatsächlich mit Zeichendaten zu füllen, oder ob sie es nur verwenden, um den Speicher mit einem bestimmten Byte-Wert zu initialisieren, stellten wir fest, dass der letztere Fall die Verwendung dominiert und nicht der erstere. Daher haben wir beschlossen, FillChar byte-zentriert zu halten.

Es stimmt, dass das Löschen eines Datensatzes mit FillChar, der ein Feld enthält, das mit einem der "verwalteten" Typen (Strings, Variant, Interface, dynamische Arrays) deklariert wurde, unsicher sein kann, wenn es nicht im richtigen Kontext verwendet wird. In dem von Ihnen angeführten Beispiel ist es jedoch tatsächlich sicher, FillChar für die lokal deklarierte Datensatzvariable solange es das erste ist, was Sie innerhalb dieses Rahmens an der Aufzeichnung tun . Der Grund dafür ist, dass der Compiler Code zur Initialisierung des String-Feldes im Datensatz erzeugt hat. Dadurch wird das String-Feld bereits auf 0 (null) gesetzt. Der Aufruf von FillChar(Foo, SizeOf(Foo), 0) überschreibt einfach den gesamten Datensatz mit 0 Bytes, einschließlich des String-Feldes, das bereits 0 ist. Die Verwendung von FillChar für die Datensatzvariable nach dem Stringfeld ein Wert zugewiesen wurde, wird nicht empfohlen. Die Verwendung der Technik der initialisierten Konstanten ist eine sehr gute Lösung für dieses Problem, da der Compiler den richtigen Code erzeugen kann, um sicherzustellen, dass die vorhandenen Datensatzwerte während der Zuweisung ordnungsgemäß abgeschlossen werden.

22voto

LU RD Punkte 33763

Wenn Sie Delphi 2009 und höher haben, verwenden Sie die Default Aufruf zum Initialisieren eines Datensatzes.

Foo := Default(TFoo); 

Ver Davids Antwort auf die Frage Wie kann man Datensätze, die verschiedene Typen enthalten, in Delphi auf einmal freigeben? .

Editar:

Der Vorteil der Verwendung des Default(TSomeType) Aufrufs ist, dass der Datensatz abgeschlossen wird, bevor er gelöscht wird. Es gibt keine Speicherlecks und keine expliziten gefährlichen Low-Level-Aufrufe an FillChar oder ZeroMem. Wenn die Datensätze komplex sind, vielleicht verschachtelte Datensätze enthalten usw., ist das Risiko von Fehlern ausgeschlossen.

Ihre Methode zur Initialisierung der Datensätze kann noch einfacher gestaltet werden:

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

Manchmal möchten Sie, dass ein Parameter einen anderen Wert als den Standardwert hat, dann gehen Sie wie folgt vor:

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

Das spart einige Tipparbeit und der Fokus wird auf das Wesentliche gelegt.

9voto

Francesca Punkte 21286

FillChar ist gut, um sicherzustellen, dass Sie keinen Müll in einer neu, uninitialisiert Struktur (Datensatz, Puffer, Array...).
Sie sollte nicht zum "Zurücksetzen" der Werte verwendet werden, ohne zu wissen, was Sie zurücksetzen.
Nicht mehr als nur schreiben MyObject := nil und erwarten, dass sie ein Speicherleck vermeiden.
Insbesondere sind alle verwalteten Arten sorgfältig zu beobachten.
Siehe die Fertigstellen Funktion.

Wenn man die Möglichkeit hat, direkt in den Speicher einzugreifen, gibt es immer eine Möglichkeit, sich selbst in den Fuß zu schießen.

4voto

Jim McKeeth Punkte 37652

FillChar wird in der Regel verwendet, um die Arrays o Datensätze mit nur numerischen Typen und Array. Sie haben Recht, dass es nicht verwendet werden sollte, wenn es Zeichenketten (oder irgendwelche ref-gezählten Variablen) in der Eintrag .

Obwohl Ihr Vorschlag, eine const zu initialisieren, funktionieren würde, kommt ein Problem ins Spiel, wenn ich eine variable Länge Array die ich initialisieren möchte.

4voto

Ian Boyd Punkte 232380

Die Frage könnte auch lauten:

Es gibt keine ZeroMemory Funktion in Windows. In den Header-Dateien ( winbase.h ) ist es ein Makro, das in der C-Welt umgedreht wird und memset aufruft:

memset(Destination, 0, Length);

ZeroMemory ist der sprachneutrale Begriff für "die Funktion Ihrer Plattform, die zum Nullsetzen des Speichers verwendet werden kann"

Das Delphi-Äquivalent von memset でございます FillChar .

Da Delphi keine Makros hat (und vor den Tagen des Inlinings), ist der Aufruf von ZeroMemory bedeutete, dass man die Strafe eines zusätzlichen Funktionsaufrufs in Kauf nehmen musste, bevor man tatsächlich zu FillChar .

In vielerlei Hinsicht ist der Aufruf FillChar ist eine Mikro-Optimierung der Leistung - die es jetzt nicht mehr gibt, da ZeroMemory wird eingefügt:

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

Bonus Lesung

Windows enthält auch die SecureZeroMemory Funktion. Sie tut genau das Gleiche wie ZeroMemory . Wenn es das Gleiche tut wie ZeroMemory warum gibt es sie?

Weil einige intelligente C/C++-Compiler erkennen könnten, dass das Setzen von Speicher auf 0 bevor man den Speicher loswird, ist Zeitverschwendung - und optimiert den Aufruf von ZeroMemory .

Ich glaube nicht, dass Delphi's Compiler so intelligent ist wie viele andere Compiler; daher gibt es keinen Bedarf für ein SecureFillChar .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X