type
TPolloNoM = record
d1: integer;
end;
type
TPolloM = record //Managed record
pollo: array of integer;
e1: integer;
end;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
function Prova(a: integer; var B: integer; const c: TForm; const d: TPolloNoM; const e: TPolloM): boolean;
public
{ Public declarations }
c1: integer;
end;
var
Form1: TForm1;
PolloNoM: TPolloNoM;
PolloM: TPolloM;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
var a1, b1: integer;
begin
a1 := 5;
b1 := 5;
Form1.c1 := 5;
PolloNoM.d1 := 5;
PolloM.e1 := 5;
Prova(a1, b1, Form1, PolloNoM, PolloM);
//Dopo la chiamata di cui sopra, che valori hanno a1, b1, Form1.c1, PolloNoM.d1, PolloM.e1 (da notare che alcuni sono passati come CONST)
end;
function TForm1.Prova(a: integer; var B: integer; const c: TForm; const d: TPolloNoM; const e: TPolloM): boolean;
var cp, dp, ep: pointer;
begin
a := 20;
b := 20;
cp := @(c as TForm1).c1;
move(b, cp^, sizeof(b));
dp := @d.d1;
move(b, dp^, sizeof(b));
ep := @e.e1;
move(b, ep^, sizeof(b));
Result := True;
end;
Senza eseguire il programma, sapete dire:
che valori hanno a1, b1, Form1.c1, PolloNoM.d1, PolloM.e1 dopo la chiamata a "Prova" (da notare che alcuni sono passati come CONST nella funzione) ?
Sciolgo il dilemma sulla "quest".
Ricordo che la funzione interessata ha la seguente definizione:
function Prova(a: integer; var B: integer; const c: TForm; const d: TPolloNoM; const e: TPolloM): boolean;
Ad una prima sommaria vista, mi aspetterei che i parametri nella funzione definiti come CONST siano appunto non modificabili all'interno della procedura, e quindi che il solo parametro "B" sia interessato dalla modifica avendo quindi il seguente risultato:
a1 rimane 5;
b1 diventa 20;
Form1.c1 dovrebbe rimanere 5;
PolloNoM.d1 dovrebbe rimanere 5;
PolloM.e1 dovrebbe rimanere 5;
Invece non è proprio così. O almeno non lo è con i normali settaggio del compilatore.
Per capire ciò, dobbiamo prima analizzare le regole riguardante il passaggio dei parametri.
Facendo un breve sunto, i parametri possono essere passati per valore o riferimento.
Senza alcun specificatore le variabili integrali (ad esempio quelle definite INTEGER, BOOLEAN, DOUBLE) sono passate per valore, cioè nello stack (o nei registri della CPU ove sia abilitato ciò) viene posto copia del valore della variabile. La procedura quindi leggerà il valore, ma l'eventuale modifica dello stesso verrà persa all'uscita della procedura stessa senza modifica del valore originale (quello del chiamante).
Quando invece come parametro passiamo una qualsiasi variabile "strutturata" (dagli array ai record, dalle classi alle interfacce) nello stack viene passato il riferimento alla variabile (quindi il puntatore alla stessa). Stessa cosa avviene quando immettiamo nella definizione della procedura il modificatore "VAR" (in questo caso viene passato il riferimento qualunque sia il tipo di variabile passato).
Quando viene passato un riferimento, TUTTE le modifiche effettuate nella procedura nelle variabili indicate come parametri si riflettono anche nelle originali.
Occorre quindi stare all'occhio e fare attenzione a come le procedure sono definite per capire cosa si fà, al fine di evitare alcune problematiche poi difficili da identificare.
Tra le varie opzioni, si può inserire la definizione di "CONST" nei parametri per istruire il compilatore a "rifiutare" e segnalare la modifica alla variabile "parametro".
Questa opzione però funziona solo per le assegnazioni esplicite (vedi l'esempio di seguito), ma NON per le assegnazioni derivate. Nel codice oggetto di questo TOPIC, io ho usato questo metodo (assegnazione derivata) aggirando il compilatore e rendondo NULLO ciò che il progettista voleva indicare: la CONST dei parametri.
DI NUOVO, fate attenzione a come usate i puntatori, perchè possono provocare danni collaterali difficili da prevedere e valutare. A mio parere, e mi sono già espresso abbondantemente su questo argomento, il ricorso all'uso dei puntattori e peggio ancora alla sua matematica dovrebbe essere limitata ai soli casi indispensabili.
Analizziamo quindi cosa accade e come i risultati siano estremamente diversi da quello che ci si aspetterebbe:
a1 rimane 5; // OK
b1 diventa 20; //OK
Form1.c1 diventa 20; //sarebbe dovuto rimanere 5;
PolloNoM.d1 diventa 20; //sarebbe dovuto rimanere 5;
PolloM.e1 diventa 20; //sarebbe dovuto rimanere 5;
a1: nella procedura l'assegnazione di a := 20 viene persa.
b1: nella procedura l'assegnazione di b := 20 viene applicata correttamente (parametro VAR)
Form1.c1, PolloNoM.d1, PolloM.e1: nella procedura questi valori vengono passati come riferimento (regola per il passaggio dei parametri)
e quindi la modifica di C, D, E anche se definiti CONST si riflette all'origine. L'uso dei puntatori aggira il compilatore VIOLANDO LA REGOLA CONST.
Se avessi tentato la modifica diretta nella procedura (esempio c.c1 := 20;), il compilatore avrebbe emesso un errore tipo" la parte sinistra non può essere assegnata".
Il tutto come curiosità.
EDIT: il comportamento indicato è specifico per Lazarus, in altri mondi il risultato è ancora più diverso (e per certi versi più complesso da analizzare). Ad esempio in Delphi PolloNoM.d1 sarebbe rimasto 5 ..... :o
Ciao ciao.