2009-04-23 21 views
26

Mi sono chiesto perché la maggior parte degli esempi Delphi utilizza FillChar() per inizializzare i record.Perché la maggior parte degli esempi Delphi utilizzano FillChar() per inizializzare i record?

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. 

Qui (http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html) è la mia nota su questo argomento. IMO, dichiara una costante con valore predefinito è un modo migliore.

risposta

34

Ragioni storiche, per lo più. FillChar() risale ai giorni Turbo Pascal ed è stato utilizzato per tali scopi. Il nome è davvero un po 'improprio perché mentre dice Fill Char(), è davvero Fill Byte(). Il motivo è che l'ultimo parametro può prendere un carattere o un byte. Quindi FillChar (Foo, SizeOf (Foo), # 0) e FillChar (Foo, SizeOf (Foo), 0) sono equivalenti. Un'altra fonte di confusione è che a partire da Delphi 2009, FillChar riempie comunque solo i byte anche se Char è equivalente a WideChar. Osservando gli usi più comuni di FillChar per determinare se la maggior parte della gente usa FillChar per riempire effettivamente la memoria con dati di carattere o semplicemente la usa per inizializzare la memoria con un determinato valore di byte, abbiamo scoperto che era l'ultimo caso a dominare il suo uso piuttosto che il primo. Con ciò abbiamo deciso di mantenere FillChar byte-centric.

È vero che la cancellazione di un record con FillChar che contiene un campo dichiarato utilizzando uno dei tipi "gestiti" (stringhe, Variant, Interfaccia, matrici dinamiche) può essere pericoloso se non utilizzato nel contesto corretto.Nell'esempio che hai fornito, tuttavia, è sicuro chiamare FillChar sulla variabile record dichiarata localmente purché sia ​​la prima cosa che fai mai per quel record all'interno di quell'ambito. Il motivo è che il compilatore ha generato il codice per inizializzare il campo stringa nel record. Questo avrà già impostato il campo stringa su 0 (zero). La chiamata a FillChar (Foo, SizeOf (Foo), 0) sovrascrive solo l'intero record con 0 byte, incluso il campo stringa che è già 0. Usando FillChar sulla variabile record dopo un valore è stato assegnato al campo stringa, è non consigliato. L'uso della tecnica costante inizializzata è un'ottima soluzione a questo problema perché il compilatore può generare il codice corretto per garantire che i valori dei record esistenti siano correttamente finalizzati durante l'assegnazione.

+2

>>> L'uso di FillChar sulla variabile record dopo che un valore è stato assegnato al campo stringa, non è consigliato In questi casi è necessario utilizzare Finalize (V); FillChar (V, SizeOf (V), 0); Se lo fai frequentemente, puoi scrivere una nuova funzione, che è Finalize + FillChar. Penso che l'utilizzo di FillChar sarà più veloce e quindi l'assegnazione a una costante, ma probabilmente non è rilevante oggi. Ancora più importante: non è necessario dichiarare una costante per ogni tipo di record. – Alex

+1

>> Penso che l'uso di FillChar sarà più veloce dell'assegnazione a una costante, ma probabilmente non è rilevante oggi. Ancora più importante: non è necessario dichiarare una costante per ogni tipo di record. << Questo è il motivo per cui considero un abuso. Sarebbe fantastico, se il compilatore supporta la parola di riserva "void", in modo che possiamo semplicemente scrivere "Foo: = void;" per inizializzare un record. – stanleyxu2005

+0

lo uso ogni volta che posso poiché mi aiuta a seguire il principio Aperto/Chiuso. quindi quando un record ha un nuovo membro (non stringa) aggiunto, non è necessaria un'ulteriore inizializzazione. –

2

Tradizionalmente, un personaggio è un singolo byte (non è più vero per Delphi 2009), in modo da utilizzare con un fillchar # 0 sarebbe inizializzare la memoria allocata in modo che conteneva solo null, o byte 0, o bin 00000000.

Si dovrebbe invece usare la funzione ZeroMemory per la compatibilità, che ha gli stessi parametri di chiamata del vecchio fillchar.

+0

Perché continui a rispondere alle stesse domande di me contemporaneamente? Questa volta mi hai battuto di 35 secondi. –

+0

deve essere qualcosa nell'acqua. – skamradt

+0

FillChar funziona come sempre. Il suo nome non è più così appropriato, ma a parte questo, non c'è differenza. –

4

fillchar viene solitamente utilizzato per riempire Array o record con soli tipi numerici e matrice. Hai ragione a non dover essere utilizzato quando ci sono stringhe (o qualsiasi variabile conteggiata per ref) nel record .

Anche se il suggerimento di utilizzare un const inizializzare che avrebbe funzionato, un problema entra in gioco quando ho un lunghezza variabileserie che voglio inizializzare.

9

FillChar va bene per assicurarsi di non ottenere alcun tipo di immondizia in una nuova struttura non inizializzata (record, buffer, arrray ...).
Non deve essere utilizzato per "ripristinare" i valori senza sapere cosa si sta ripristinando.
Non più che scrivere semplicemente MyObject := nil e aspettarsi di evitare perdite di memoria.
In particolare tutti i tipi gestiti devono essere guardati attentamente.
Vedere la funzione Finalize.

Quando hai il potere di armeggiare direttamente con la memoria, c'è sempre un modo per spararti ai piedi.

+0

mi è successo, prima, a volte non ho usato fillchar per il mio record, e alcuni membri di quel disco dovrebbero avere la stringa dei rifiuti, da allora uso sempre fillchar o zeromemory. – avar

+0

Per Francois: non ho chiesto di confermare questo problema. Capisco che non ci sono problemi se viene usato/attentamente /. Mi chiedevo solo, perché FillChar è stato usato per così tanti anni per intializzare i record. – stanleyxu2005

1

Questa domanda ha una più ampia implicazione che è stata nella mia mente da secoli. Anch'io, sono stato educato a usare FillChar per i record. Questo è bello perché spesso aggiungiamo nuovi campi al record (dati) e ovviamente FillChar (Rec, SizeOf (Rec), # 0) si occupa di questi nuovi campi. Se "lo facciamo correttamente", dobbiamo scorrere tutti i campi del record, alcuni dei quali sono tipi enumerati, alcuni dei quali possono essere i record stessi e il codice risultante è meno leggibile e potrebbe essere errato se non aggiungiamo nuovo registrare i campi ad esso diligentemente. I campi stringa sono comuni, quindi FillChar è un no-no ora. Alcuni mesi fa, sono andato in giro e ho convertito tutti i miei FillChar su record con campi stringa per la cancellazione iterata, ma non ero soddisfatto della soluzione e mi chiedo se c'è un modo pulito di fare il 'Fill' su tipi semplici (ordinale/float) e 'Finalize' su varianti e stringhe?

+1

La funzione 'Finalizza' aiuta. Ma suggerisco ancora di farlo correttamente assegnandolo a un record const vuoto. Una volta modificata la struttura del tuo record, il compilatore ti ricorderà di aggiornare anche questo record vuoto. – stanleyxu2005

+0

Brian, guarda la mia risposta e segui il link. Questa è la risposta che stai cercando :) –

+0

Finalize + ZeroMemory/FillChar coprirà tutti i casi e soddisferà tutte le esigenze. E probabilmente è più veloce dell'assegnazione a record vuoti costanti. – Fr0sT

12

Se si dispone di Delphi 2009 e in seguito, utilizzare la chiamata Default per inizializzare un record.

Foo := Default(TFoo); 

Vedi David's answer alla domanda How to properly free records that contain various types in Delphi at once?.

Edit:

Il vantaggio di utilizzare la chiamata Default(TSomeType), è che il record è finalizzato prima della cancellazione. Nessuna perdita di memoria e nessuna chiamata pericolosa di basso livello a FillChar o ZeroMem. Quando i record sono complessi, forse contenenti record annidati, ecc, viene eliminato il rischio di commettere errori.

Il tuo metodo per inizializzare i record può essere reso ancora più semplice:

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

a volte si vuole un parametro per avere un valore non predefinito, quindi fare come questo:

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

Questa volontà salva un po 'di digitazione e il focus è impostato sulle cose importanti.

+0

Grazie per le tue informazioni. IMO, per i vecchi compilatori delphi dovremmo definire alcune costanti di record predefinite piuttosto che usare la funzione Magic FillChar. Per i moderni compilatori Delphi, dovremmo dichiarare anche i costruttori per i tipi di record. Se hai interessi, puoi eseguire il checkout dei progetti open source dalla mia pagina del profilo. Codigo questi progetti secondo il principio che ho indicato nel commento. – stanleyxu2005

+0

Aggiunte alcune semplificazioni per definire i record const. –

+1

Grazie per la meravigliosa() costruzione, non ho idea che Delphi possa farlo. – Fr0sT

Problemi correlati