Italian community of Lazarus and Free Pascal

Programmazione => Generale => Topic aperto da: sanric - Luglio 24, 2023, 06:07:23 pm

Titolo: Il peso dei record
Inserito da: sanric - Luglio 24, 2023, 06:07:23 pm
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...

Codice: [Seleziona]
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:
Codice: [Seleziona]
Il record pesa 56 bytes.

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
Codice: [Seleziona]
Pertanto, la dimensione totale del record è 4 + 4 + 8 + 20 + 8 = 56 byte.
  ;D
Titolo: Re:Il peso dei record
Inserito da: DragoRosso - Luglio 24, 2023, 09:06:44 pm
Ciao, quello che dici è vero se prima di "record" ci aggiungi la parolina "packed" ... ;D

Tutte le strutture vengono riempite con dei "pad" cioè del byte nulli (a zero) per allineare i dati.
Questo operazione consente di ottimizzare il codice compilato ed è praticamente obbligatorio per avere un codice veloce.

Se è presente "packed" allora il compilatore non inserirà alcun "pad" e il record verrà gestito in maniera integrale.

[OT]
Il modo di riempimento (cioè la posizione dei pad) non può essere previsto in maniera assoluta, anche se ci sono delle regole, e ciò fà si che l'uso dei PUNTATORI E DELLA LORO MATEMATICA per l'accesso generico al record può generare inconvenienti difficili da prevedere e da diagnosticare.

Questo è uno dei motivi per cui "odio" l'uso dei puntatori espliciti con le variabili.

Ciao
Titolo: Re:Il peso dei record
Inserito da: DragoRosso - Luglio 24, 2023, 09:11:56 pm
Il record pesa 56 bytes.
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
Codice: [Seleziona]
Pertanto, la dimensione totale del record è 4 + 4 + 8 + 20 + 8 = 56 byte.
  ;D

Bard (ma il suo compare non è da meno) è l'esempio tipico della AI: ciò che riportano magari è anche giusto, ma non si sà il perchè ....  ;)
Titolo: Re:Il peso dei record
Inserito da: sanric - Luglio 25, 2023, 07:12:43 am
Ciao, quello che dici è vero se prima di "record" ci aggiungi la parolina "packed" ... ;D

Tutte le strutture vengono riempite con dei "pad" cioè del byte nulli (a zero) per allineare i dati.
[cut]

Grazie mille DragoRosso per la gentile risposta.
Quindi non è del tutto vero che le variabili che fanno parte della "variant part" (così vengono definite nell'Object Pascal Language Guide di Borland) non sono allocate preventivamente. O sbaglio?
Titolo: Re:Il peso dei record
Inserito da: DragoRosso - Luglio 25, 2023, 08:01:32 am
Le parti "variant" dei record sono allocate preventivamente sulla lunghezza più lunga dei campi interessati, in realtà ci pensa il compilatore.

I record hanno sempre lunghezza fissa, perchè le definizioni che hanno lunghezza variabile (ad esempio una "string") vengono allocate come puntatore. La "lunghezza" di un record non cambia durante la vita dello stesso.

Per inserire i "pad", il compilatore si basa sull'allineamento predefinito (in un 64 bit l'allineamento è a 64 bit, ossia quad word o 8 byte) o l'allineamento in essere in quella parte di codice.

Quindi, supponiamo che l'allineamento predefinito non venga modificato allora qualsiasi variabile (campo) di un record viene allineato a quella lunghezza o suoi multipli inserendo dei byte a zero. Questo in generale. Ogni compilatore può usare una propria tecnica per il riempimento.

Ciao
Titolo: Re:Il peso dei record
Inserito da: sanric - Luglio 26, 2023, 07:00:22 am
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:

Codice: [Seleziona]
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:

Codice: [Seleziona]
Napoleone Bonaparte
Residenza: Parigi

Logicamente mi sembra che la cosa non torni: dove mi sto sbagliando?
Titolo: Re:Il peso dei record
Inserito da: nomorelogic - Luglio 26, 2023, 09:51:57 am
ciao sanric

il fatto è che la parti varianti del record, condividono la memoria e questo è il motivo per cui sono stati implementati.
Questo in modo da permettere di vedere quella parte di memoria come un campo unico oppure come più campi.

Sta a chi codifica utilizzare il record variante in modo congruo.
Nel tuo caso tramite una "if" potresti decidere quale variante utilizzare.

Leggi qua per maggiori spiegazioni.
https://wiki.freepascal.org/Record#Variable_structure (https://wiki.freepascal.org/Record#Variable_structure)
Titolo: Re:Il peso dei record
Inserito da: DragoRosso - Luglio 26, 2023, 10:45:13 am
Per completare quanto detto da @nomorelogic, la memoria condivisa (che come ti accennavo è lunga quanto la lunghezza massima del campo più lungo + eventuali pad) è diciamo in formato raw. Cosa significa ?

Significa che inserendo una stringa nel campo residenza o un data nel campo datadecesso ciò che "leggerai" usando entrambe le variabili sarà congruo solo ed esclusivamente se leggerai il campo corretto.

Se inserisci una data (il cui formato interno è un double) tale valore potrà essere riletto correttamente solo se accederai al dato "datadecesso", idem per la residenza. E fai attenzione che leggendo ad esempio la "datadecesso" avendo inserito come ultimo dato una stringa in "residenza" potrebbe provocare una eccezione (perchè un double ha una rappresentazione ben precisa e quindi potrebbe essere generato un NAN ... "not a number").

Nel tuo esempio hai inserito il valore "Parigi" come residenza, prova e leggere il campo "datadecesso" e vedi cosa ti riporta ...

Ciao
Titolo: Re:Il peso dei record
Inserito da: sanric - Luglio 27, 2023, 07:09:43 am

Nel tuo esempio hai inserito il valore "Parigi" come residenza, prova e leggere il campo "datadecesso" e vedi cosa ti riporta ...

Ho fatto la prova: FPC mi ha restituito il valore "30/12/99", quasi fosse una sorta di valore di default.
Mah, forse chi ha costruito FPC ha ascoltato molto Prince!  ;D
Titolo: Re:Il peso dei record
Inserito da: DragoRosso - Luglio 27, 2023, 10:26:44 am
Ho fatto la prova: FPC mi ha restituito il valore "30/12/99", quasi fosse una sorta di valore di default.
Mah, forse chi ha costruito FPC ha ascoltato molto Prince!  ;D

 :D In realtà quella è la rappresentazione in formato datetime della stringa che hai inserito (non ho notizia che 30/12/99 sia un qualche default).

Giusto per dire che con le parti variant dei dati occorre fare molta attenzione. Io in genere preferisco inserire un campo che definisce il tipo di dato rappresentato in un altro campo (anche variant, anche se non mi piace molto) in modo che sia inequivocabile la cosa, e con l'accesso protetto sia in lettura che in scrittura tale per cui l'errore è pressochè impossibile.

Ti posto appena possibile un esempio di codice.

Ciao
Titolo: Re:Il peso dei record
Inserito da: sanric - Luglio 27, 2023, 10:56:12 am

Ti posto appena possibile un esempio di codice.

Grazie mille, sono in debito!
Titolo: Re:Il peso dei record
Inserito da: nomorelogic - Luglio 27, 2023, 03:33:39 pm
Ho fatto la prova: FPC mi ha restituito il valore "30/12/99", quasi fosse una sorta di valore di default.
Mah, forse chi ha costruito FPC ha ascoltato molto Prince!  ;D

 :D In realtà quella è la rappresentazione in formato datetime della stringa che hai inserito (non ho notizia che 30/12/99 sia un qualche default).

30/12/99 in effetti suona strano
me se fosse 30/12/1899 (e probabilmente lo è) allora sarebbe un valore per rappresentare una data zero
potrebbe essere che, non essendo presente nell'area di memoria, una data valida, venga restituito 30/12/1899


Edit:
potresti verificare la data esatta formattando tipo "yyyy-mm-dd"?
Titolo: Re:Il peso dei record
Inserito da: sanric - Luglio 27, 2023, 03:57:53 pm

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):

Codice: [Seleziona]
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):

Codice: [Seleziona]
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.

Titolo: Re:Il peso dei record
Inserito da: nomorelogic - Luglio 27, 2023, 04:00:57 pm
essendo così, quella data del 1899 la puoi usare come costante per verificare se è presente un valore oppure no
Titolo: Re:Il peso dei record
Inserito da: sanric - Luglio 27, 2023, 04:14:04 pm
Certo, sapendolo posso gestire la cosa abbastanza comodamente con un ciclo if.. then.

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]).

Vabbé, mi fermo qui altrimenti la cosa diventa pesante e non so quanto interessante per gli altri utenti del forum.
Titolo: Re:Il peso dei record
Inserito da: DragoRosso - Luglio 27, 2023, 04:45:38 pm
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:

Codice: [Seleziona]
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".
Titolo: Re:Il peso dei record
Inserito da: DragoRosso - Luglio 27, 2023, 05:10:25 pm
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è:
Codice: [Seleziona]
var
  p: string[3];
e
Codice: [Seleziona]
var
  p: string;
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:
Codice: [Seleziona]
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
Titolo: Re:Il peso dei record
Inserito da: nomorelogic - Luglio 27, 2023, 05:54:06 pm
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 :)

Codice: [Seleziona]
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;
Titolo: Re:Il peso dei record
Inserito da: DragoRosso - Luglio 27, 2023, 07:03:13 pm
@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.

Codice: [Seleziona]
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
Titolo: Re:Il peso dei record
Inserito da: nomorelogic - Luglio 27, 2023, 07:16:01 pm
sono anziano, le cose a volte mi sfuggono  ;D