Il tipo string detto anche AnsiString o Long String è stato introdotto in alternativa al tipo Short String che aveva una limitazione a 255 caratteri. Come altri tipi quali Variant, OleVariant , Interface, dispInterface e Dynamic Array, rientra in un tipo di dati Life-Managed chiamati anche Garbage Collected type, perché possono condividere risorse e la loro esistenza e presenza è tenuta sotto controllo da un contatore.
Un altra loro caratteristica è che al momento della creazione sono inizializzati a zero o a qualche valore che li identifichi come non-usati.
Il compilatore in presenza di questo tipi di dati aggiunge che un blocco try-finally-end con il quale gestisce gli errori e garantisce il cleanup relativo alle variabili garbage collected.
Quello che deve essere chiaro è che una string in delphi non è un semplice puntatore a un array di caratteri come avviene in altri linguaggi. La sua struttura è più complessa ma anche più sicura (terminano sempre con il carattere nullo).
La assegnazione di una stringa implicitamente include una allocazione di memoria per i caratteri che la compongono:
var
s:string //dichiarazione variabile
begin
s:='ciao'; //assegnazione
Ora potremo fare riferimento ad essa come se fosse un array anche se in realtà non lo è.
s[1]:='m'; //"ciao" -> "miao"
Questo non avremo potuto farlo se prima non ci fosse stata una assegnazione, dato che lo spazio di memoria riservato alla stringa non sarebbe stato ancora allocato.
In questo articolo cercheremo di descrivere con un esempio la struttura dati di una stringa (string type). Per fare questo ci serviremo del seguente programma:
program StringType;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
s:string;
begin
s:='ciao'; //assegnazione variabile
writeln(s); //uso della variabile
readln;
end.
Vediamo in dettaglio la prima istruzione di assegnazione:
Indirizzo Codice Comandi Commenti
// s:='ciao';
0040915A B808F94000 mov eax,$0040f908 //indirizzo destinazione
0040915F BAB4914000 mov edx,$004091b4 //indirizzo sorgente
00409164 E80FB4FFFF call @LStrAsg
L'assegnazione del contenuto ad una variabile di tipo stringa avviene tramite la funzione LStrAsg, la quale prende in ingresso due parametri:
- in EAX (destinazione) l'indirizzo di una cella di memoria che contiene l'indirizzo del primo carattere della stringa
EAX-> s -> s.stringa_destinazione[1] - in EDX (sorgente) l'indirizzo del primo carattere della stringa da inserire
EDX -> stringa_sorgente[1]
Per verificare quanto detto vediamo cosa contengono le celle di memoria a cui ci riferiamo:
Indirizzo Codice hex Codice decodificato
0040F908 80 C2 A1 00 00A1C280
Indirizzo Codice hex ASCII
00A1C280 63 69 61 6F 00 ciao.
Indirizzo Codice hex ASCII
004091B4 63 69 61 6F 00 ciao.
Il primo indirizzo è la variabile che ho dichiarato, esso rimarrà vuoto fino a che non gli verrà assegnato il contenuto.
Il contenuto è il puntatore alla struttura dati che contiene la stringa da inserire.
Giusto per essere chiari il tipo "stringa" in memoria è un record composto da diversi campi, e solo l'ultimo dei quali contiene la stringa vera e propria. Quindi per ogni stringa troviamo una prima parte composta da un record del tipo StrRec e dopo di esso inizia la stringa.
StrRec = packed record
refCnt: Longint;
length: Longint;
end;
Nell'esempio precedente avremo infatti :
Indirizzo Codice Decod. o ASCII Commenti
00A1C278 01 00 00 00 1 ;refCnt
00A1C27C 04 00 00 00 4 ;dimensione
00A1C280 63 69 61 6F 00 ciao ;stringa
Se proseguiamo nell'analisi del procedimento potremo osservare come questa stringa verrà utilizzata:
Indirizzo Codice Comandi Commenti
// writeln(s);
00409169 A1F0A94000 mov eax,[$0040a9f0]
0040916E 8B1508F94000 mov edx,[$0040f908]
00409174 E8C3B7FFFF call @Write0LString
00409179 E88AA4FFFF call @WriteLn
0040917E E8599CFFFF call @_IOTest
quindi la variabile per il compilatore rimarrà sempre all'indirizzo 0040F908 e di fatto verrà usato come un puntatore alla stringa vera e propria.
Questa variabile viene allocata nella sezione .bss (Block Started by Symbol) che è usata dai compilatori e linker come area dati contenente le variabili statiche che sono inizialmente impostate al valore zero (ad es. quando inizia l'esecuzione di un programma). Solitamente il program loader inizializza la memoria allocata per il bss all'atto del caricamento del programma stesso, dopo di che alle variabili verrà assegnato il rispettivo contenuto.
Il contenuto in questione invece sta in un altra sezione che è esterna al modulo che contiene il processo:
Memory map, item 31
Address = 00900000
Size = 00140000 (1310720.)
Owner = 00900000 (self)
Section =
Contains =
Type = Priv 00021004
Access = RW
Initial access = RW
Mapped as =
Se è semplice capire i vantaggi d'avere un campo Length in una string, non è altrettanto immediato sapere a cosa serve il campo RefCnt. Questo campo contiene un numero intero e che viene chiamato Reference Counter. Tutte le operazioni di gestione di questo campo sono in carico alla funzione LStrAsg, che più o meno procede così:
Se la stringa di destinazione non è vuota allora :
1 - Decrementa il contatore della stringa di destinazione;
2 - Se se il contatore raggiunge il valore zero allora :
2.1 - Rilascia (libera) la stringa di destinazione;
3 - Se la stringa sorgente non è vuota allora :
3.1 - Incrementa il contatore della stringa sorgente;
4 - Assegna la stringa sorgente alla stringa destinazione
Tutto questo rientra in una logica di Garbage Collection, cioè più oggetti o blocchi di memoria possono essere usati da altri oggetti, per tenere traccia di questi link si usa un contatore, perciò dei dati possono essere liberati, cioè lo spazio dove risiedono può essere considerato libero, solo se questo contatore è zero, ovvero non è utilizzato da nessuno. Al contrario ogni volta che dei dati, come ad esempio una stringa viene assegnata, il suo contatore viene incrementato, ed indicherà quanti puntatori stanno facendo riferimento ad essa.





