Salve a tutti, è la prima volta che vi chiedo qualcosa ma sono molto "niubbo" e non riesco a venirne fuori da questa semplice cosa. ;)
Se scrivo il seguente programma...
program unRecord;
uses SysUtils;
type
iMieiDati = record
nome:string; //8 bytes
cognome:string; //8 bytes
dataDiNascita:TDateTime; // 8 bytes
case vivente:Boolean of //1 byte
True: (residenza:string[20]); //20 bytes
False: (dataDecesso:TDateTime); //8 bytes
end;
var
imperatore:iMieiDati;
begin
with imperatore do
begin
nome:='Napoleone';
cognome:='Bonaparte';
dataDiNascita:=EncodeDate(1769,8,15);
vivente:=False;
dataDecesso:=EncodeDate(1821,5,5);
end;
WriteLn('Il record pesa ',sizeof(imperatore),' bytes.');
end.
e lo eseguo, nel mio sistema a 64 bit, risulta che:
Ora, accanto ad ogni singola variabile, ho segnato in commento il valore che di essa mi viene fornito tramite il comando sizeof().
Da qui la domanda: perché il valore usato nell'esempio è 56 e non 33?
Non ho usato tutti i campi ma mi pare che vengano allocati lo stesso da FPC: ad esempio la variabile residenza viene allocata lo stesso anche nel caso in cui il case... of si riveli falso.
Se volessi essere sicuro che il compilatore non vada ad usare più memoria del necessario, come posso fare?
Beh, mi sembra che come primo approccio ci sia abbastanza carne al fuoco.
Grazie mille a tutti per le gentili risposte.
PS: ho fatto la stessa domanda sia a Bard che a ChatGPT. Ne è risultato che i limiti di quelle tecnologie sono davvero tanti.
Tanto per dire, Bard è rimandato a settembre in matematica dal momento che è convinto che
Pertanto, la dimensione totale del record è 4 + 4 + 8 + 20 + 8 = 56 byte.
;D
Approfitto della vostra gentilezza per comprendere qualcosa che davvero mi sfugge e cioè: perché le "variant parts" di una struttura record, pur avendo un'istruzione condizionale al proprio interno, non la rispettano?
Faccio un piccolo esempio qui sotto:
program variantPart;
type
TPersona = record
nome:string;
cognome:string;
dataDiNascita:TDateTime;
case vivente:Boolean of
True: (residenza:string[20]);
False: (dataDecesso:TDateTime);
end;
var
imperatore:TPersona;
begin
with imperatore do
begin
nome:='Napoleone';
cognome:='Bonaparte';
vivente:=False;
residenza:='Parigi'; \\ <----
end;
WriteLn(imperatore.nome,' ',imperatore.cognome);
WriteLn('Residenza: ',imperatore.residenza);
end.
In teoria, essendo per l'appunto Napoleone morto da un bel pezzo, la variabile residenza non dovrebbe essere contemplata ed il compilatore avrebbe dovuto darmi un bel messaggio d'errore.
Invece tutto fila liscio come l'olio con il seguente output:
Napoleone Bonaparte
Residenza: Parigi
Logicamente mi sembra che la cosa non torni: dove mi sto sbagliando?
potresti verificare la data esatta formattando tipo "yyyy-mm-dd"?
Ok, ho fatto la prova usando questo codice (più che altro un recap se qualche niubbo come me si fosse perso nel frattempo):
program variantPart;
uses
SysUtils, DateUtils;
type
TPersona = record
nome:string;
cognome:string;
dataDiNascita:TDateTime;
case vivente:Boolean of
True: (residenza:string[20]);
False: (dataDecesso:TDateTime);
end;
var
imperatore:TPersona;
begin
with imperatore do
begin
nome:='Napoleone';
cognome:='Bonaparte';
vivente:=False;
residenza:='Parigi'; // <----
WriteLn(nome,' ',cognome);
WriteLn('Residenza: ',residenza);
WriteLn(FormatDateTime('yyyy-mm-dd',dataDecesso));
end;
end.
Il risultato è codesto (e avevi ragione tu):
Napoleone Bonaparte
Residenza: Parigi
1899-12-30
Come sospettato, sembra essere un valore di default per indicare una data "0", in modo da non incappare nel rischio ipotizzato da DragoRosso di un NaN.
Se così fosse mi sembrerebbe una mossa intelligente.
Non c'è problema sulla discussione, approfondire và comunque bene.
Riguardo al valore, non contarci molto sul fatto che rimanga a zero quando non è valida. Il dato dipende dalla stringa e anche se una buona parte darà zero come data (che secondo me comprende la rappresentazione di un NAN per le date), ci saranno anche i casi in cui la data non sarà zero.
Per quello che riguarda la condivisione e quindi la rappresentazione come variante in realtà è utile in diversi casi (sempre meno, però ...), come ad esempio:
Esempio per condividere aree di memoria: in questo caso condivide un valore single (32 bit) con la sua rappresentazioe a byte.
type
TConverterRec = record
case Boolean of
false: (ByteArray: array[0..3] of Byte);
true: (FloatValue: Single);
end;
var
Pippo: TConverterRec;
procedure TForm1.Button1Click(Sender: TObject);
var s: single;
begin
s:= 0.43; //$3EDC28F6 Valore 0,43 in single float
Pippo.ByteArray[0] := $F6;
Pippo.ByteArray[1] := $28;
Pippo.ByteArray[2] := $DC;
Pippo.ByteArray[3] := $3E;
ShowMessage(floattostr(Pippo.FloatValue));
end;
Ovviamente sono situazioni che è possibile usare, come il GOTO ( :-X ) ma che volendo si possono anche porre in oblio.
Ciao
P.S.: ho citato l'utilità, ad esempio quando si ha a che fare con vecchi PLC dove non è possibile leggere un valore float se non a byte, e con questo trucco è comoda e abbastanza sicura la "conversione".
Tuttavia, anche se ho compreso le spiegazioni fornite, trovo il fatto di allocare lo stesso la memoria per le variabili comprese in una variant-part un po' fuorviante.
Se non fossero allocate sarebbe più logico e semplice poter effettuare un controllo in fase di parsing del codice, evitando errori.
Inoltre, se il padding è il vero problema, non comprendo perché non poter utilizzare anche nelle variant-part dei puntatori "mascherati" (ad es. string al posto di string[xxx]).
Se un campo rappresenta una variabile "complessa", come un oggetto o una stringa o ..., allora il campo diventa un puntatore. Il tutto viene mascherato dal compilatore e quindi il programmatore non deve preoccuparsi. L'unico eventuale problema, che però è abbastanza diffuso, sarebbe quello di fare eventualmente la copia "in un colpo solo" del record ... cosa che non si può fare se il record ha appunto dei campi con variabili non integrali.
Lavorare quindi con i puntatori (simil "C") è abbastanza rischioso perchè:
e
hanno internamente due rappresentazioni diverse (questo in generale, poi si possono forzare alcuni comportamenti con l'uso delle opzioni di compilazione).
Inoltre provate a pensare un domani se cambi qualcosa nelle dichiarazioni e devi andare a rivederti tutti gli usi fatti con quella variabile ... un suicidio.
Meglio usare le "cose" ben definite e intrinseche nel linguaggio, in modo da evitare il più possibile bagni di sangue.
Faccio ancora un esempio banale:
var
p: array [0..4] of integer;
i: integer;
//NON FATELO COSI !!!!!!!!!!!!!!!!!!!!!!!!!
for i := 0 to 4 do
p[i] := i; //è solo un esempio di assegnazione
//Se per qualche motivo devo variare la lunghezza dell'array diventerò scemo a correggere i for, i while e chissà cos'altro.
//FATE COSI'
for i:= Low(p) to High(p) do
p[i] := i; //è solo un esempio di assegnazione
//Questo funzionerà sempre senza alcuna correzione
Ciao
rimetto il link di qualche risposta fa
https://wiki.freepascal.org/Record#Variable_structure (https://wiki.freepascal.org/Record#Variable_structure)
ci trovate questo esempio che segue dove, con lo stratagemma della memoria condivisa, si può vedere una variabile come word, byte alto e byte basso o, udite udite, come sequenza di bit
direi che è una funzionalità veramente unica :)
type
TSpecialWord = record
case Byte of
0: (Word_value: Word); // 7
1: (Byte_low, Byte_high: Byte); // 7, 0
2: (Bits: bitpacked array [0..15] of 0..1); // 1, 1, 1, 0, 0, ...
end;
@nomorelogic anche esempio postato in testa a questa seconda pagina del topic (condivisione single con byte :D ).
Questo è un esempio di codice, create una app e inserite un TButton, fate doppio click (per generare l'evento onclick) e sovrascrivete tutto il codice con questo.
E' solo un esempio.
unit Unit1;
{$IFDEF FPC}
{$mode Delphi}{$H+}
{$modeswitch advancedrecords}
{$ENDIF}
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, DateUtils, StdCtrls, TypInfo, Rtti;
type TEnumTipoDato = (Nullo=0, StatoNasc, DataNasc);
type TAnagrafica = record
Nome: string;
Cognome: string;
function InserisciDato(Value: string): WordBool; overload;
function InserisciDato(Value: TDateTime): WordBool; overload;
function LeggiDato(var DatoLetto: variant): TEnumTipoDato;
//INTERNALLY USED, DON'T CALL EXPLICYTY
{$IFDEF FPC}
class operator Initialize (var Dest: TAnagrafica);
{$ELSE}
class operator Initialize (out Dest: TAnagrafica);
{$ENDIF}
//INTERNALLY USED, DON'T CALL EXPLICYTY
class operator Finalize (var Dest: TAnagrafica);
strict private
TipoDato: TEnumTipoDato;
Dato: string;
end;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
Anagrafica: TAnagrafica;
implementation
{$R *.lfm}
{$IFDEF FPC}
class operator TAnagrafica.Initialize (var Dest: TAnagrafica);
{$ELSE}
class operator TAnagrafica.Initialize (out Dest: TAnagrafica);
{$ENDIF}
begin
dest.Nome := '';
dest.Cognome := '';
dest.TipoDato := Nullo;
dest.Dato := '';
end;
class operator TAnagrafica.Finalize (var Dest: TAnagrafica);
begin
;
end;
function TAnagrafica.InserisciDato(Value: string): WordBool;
begin
Dato := Value;
TipoDato := StatoNasc;
Result := true;
end;
function TAnagrafica.InserisciDato(Value: TDateTime): WordBool;
begin
Dato := DateTimetoStr(Value);
TipoDato := DataNasc;
Result := true;
end;
function TAnagrafica.LeggiDato(var DatoLetto: variant): TEnumTipoDato;
begin
Result := TipoDato;
case TipoDato of
Nullo:
begin
DatoLetto := '';
end;
StatoNasc:
begin
DatoLetto := Dato;
end;
DataNasc:
begin
DatoLetto := StrToDateTime(Dato);
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
tempdato: variant;
p: TEnumTipoDato;
begin
//
Anagrafica.InserisciDato(now);
//Leggi il dato: .. il dato è in vari formati, dipende da come è stato inserito
p := Anagrafica.LeggiDato(tempdato);
ShowMessage(VarToStr(tempdato));
//
Anagrafica.InserisciDato('Ahhh ... Parigi');
//Leggi il dato: .. il dato è in vari formati, dipende da come è stato inserito
p := Anagrafica.LeggiDato(tempdato);
ShowMessage(VarToStr(tempdato));
end;
end.
Ciao