Quali variabili sono inizializzate quando in Delphi?

Quindi ho sempre sentito che i campi di class (basati su heap) sono stati inizializzati, ma le variabili basate sullo stack non lo erano. Ho anche sentito che anche i membri dei record (anch’essi basati sullo stack) non sono stati inizializzati. Il compilatore avverte che le variabili locali non sono inizializzate ([Avviso DCC] La variabile W1036 ‘x’ potrebbe non essere stata inizializzata), ma non avvisa per i membri del record. Quindi ho deciso di eseguire un test.

Ottengo sempre 0 da Integers e false da Booleans per tutti i membri del record.

Ho provato a distriggersre varie opzioni del compilatore (debug, ottimizzazioni, ecc.), Ma non c’era differenza. Tutti i miei membri del record sono stati inizializzati.

Cosa mi manca? Sono su Delphi 2009 Update 2.

program TestInitialization; {$APPTYPE CONSOLE} uses SysUtils; type TR = Record Public i1, i2, i3, i4, i5: Integer; a: array[0..10] of Integer; b1, b2, b3, b4, b5: Boolean; s: String; End; var r: TR; x: Integer; begin try WriteLn('Testing record. . . .'); WriteLn('i1 ',R.i1); WriteLn('i2 ',R.i2); WriteLn('i3 ',R.i3); WriteLn('i4 ',R.i4); WriteLn('i5 ',R.i5); Writeln('S ',Rs); Writeln('Booleans: ', R.b1, ' ', R.b2, ' ', R.b3, ' ', R.b4, ' ', R.b5); Writeln('Array '); for x := 0 to 10 do Write(Ra[x], ' '); WriteLn; WriteLn('Done . . . .'); except on E:Exception do Writeln(E.Classname, ': ', E.Message); end; ReadLn; end. 

Produzione:

 Record di prova  .  .  .
 i1 0
 i2 0
 i3 0
 i4 0
 i5 0
 S
 Booleans: FALSE FALSE FALSE FALSE FALSE
 schieramento
 0 0 0 0 0 0 0 0 0 0 0
 Fatto .  .  .  .

Le variabili globali sono inizializzate a zero. Le variabili utilizzate nel contesto del principale begin .. il blocco end di un programma può essere un caso speciale; a volte vengono trattati come variabili locali, in particolare for indicizzatori -loop. Tuttavia, nel tuo esempio, r è una variabile globale e allocata dalla sezione .bss dell’eseguibile, che il caricatore di Windows garantisce è a zero.

Le variabili locali vengono inizializzate come se fossero state passate alla routine Initialize . La routine Initialize utilizza runtime type-info (RTTI) per i campi di azzeramento (in modo ricorsivo – se un campo è di un array o tipo di record) e array (in modo ricorsivo – se il tipo di elemento è un array o un record) di un tipo gestito , dove un tipo gestito è uno di:

  • AnsiString
  • UnicodeString
  • WideString
  • un tipo di interfaccia (compresi i riferimenti al metodo)
  • tipo di array dinamico
  • Variante

Le allocazioni dall’heap non sono necessariamente inizializzate; dipende da quale meccanismo è stato utilizzato per allocare memoria. Le allocazioni come parte dei dati dell’object di istanza vengono riempite a zero da TObject.InitInstance . Le allocazioni da AllocMem sono riempite a zero, mentre GetMem allocazioni GetMem non sono riempite a zero. Le allocazioni da New vengono inizializzate come se fossero state passate a Initialize .

Ottengo sempre 0 da Integers e false da Booleans per tutti i membri del record.

Ho provato a distriggersre varie opzioni del compilatore (debug, ottimizzazioni, ecc.), Ma non c’era differenza. Tutti i miei membri del record sono stati inizializzati.

Cosa mi manca?

Beh, a parte il tuo test che utilizza variabili globali anziché locali: la cosa importante che ti manca è la distinzione tra le variabili che sembrano essere inizializzate casualmente e le variabili che vengono inizializzate in modo formale .
BTW : Questo è il motivo per cui i programmatori che non controllano i loro avvertimenti fanno l’errore comune di presumere che il loro codice scritto male si comporti correttamente quando i pochi test fanno; capita di avere 0 e false impostazioni predefinite …. Want To Buy: random initialisation of local variables for debug builds.

Considera la seguente variazione sul tuo codice di test:

 program LocalVarInit; {$APPTYPE CONSOLE} procedure DoTest; var I, J, K, L, M, N: Integer; S: string; begin Writeln('Test default values'); Writeln('Numbers: ', I:10, J:10, K:10, L:10, M:10, N:10); Writeln('S: ', S); I := I + 1; J := J + 2; K := K + 3; L := L + 5; M := M + 8; N := N + 13; S := 'Hello'; Writeln('Test modified values'); Writeln('Numbers: ', I:10, J:10, K:10, L:10, M:10, N:10); Writeln('S: ', S); Writeln(''); Writeln(''); end; begin DoTest; DoTest; Readln; end. 

Con il seguente esempio di output:

 Test default values Numbers: 4212344 1638280 4239640 4239632 0 0 S: Test modified values Numbers: 4212345 1638282 4239643 4239637 8 13 //Local vars on stack at end of first call to DoTest S: Hello Test default values Numbers: 4212345 1638282 4239643 4239637 8 13 //And the values are still there on the next call S: Test modified values Numbers: 4212346 1638284 4239646 4239642 16 26 S: Hello 

Gli appunti

  • L’esempio funziona meglio se si compila con l’ottimizzazione distriggersta. Altrimenti, se hai ottimizzazione su:
    • Alcuni vars locali saranno manipolati nei registri della CPU.
    • E se si visualizza lo stack della CPU mentre si passa attraverso il codice, si noterà ad esempio che I := I + 1 non modifica nemmeno lo stack. Quindi ovviamente il cambiamento non può essere portato a termine.
  • Potresti sperimentare diverse convenzioni di chiamata per vedere come ciò influisce sulle cose.
  • È anche ansible verificare l’effetto dell’impostazione dei vars locali su zero invece di incrementarli.
  • Questo dimostra come sei completamente dipendente da ciò che si è trovato nella pila prima che il tuo metodo venisse chiamato.

Nota che nel codice di esempio che hai fornito, il record è in realtà una variabile globale, quindi sarà completamente inizializzato. Se si sposta tutto il codice su una funzione, questa sarà una variabile locale e quindi, in base alle regole fornite da Barry Kelly, verrà inizializzato solo il suo campo stringa (su “”).

Ho una situazione simile e ho pensato lo stesso, ma quando aggiungo altre variabili usate prima del record, i valori diventano inutili, quindi prima di usare il mio record dovevo inizializzarmi usando

 FillChar(MyRecord, SizeOf(MyRecord), #0)