Gestire con un Puntatore le Strutture esterne
Come descritto anche nella pagina dedicata alla gestione in modo sicuro delle Strutture esterne, la lettura e la scrittura effettuate su Strutture esterne di una certa complessità è sempre molto difficile. Tanto è che, nella pagina segnalata nel collegamento, si è proposto nei casi più difficili di creare un'apposita libreria dinamica .so esterna scritta in C, nella quale gestire con le risorse del linguaggio C i membri di tali Strutture molto complesse sia in lettura che in scrittura.
La difficoltà della gestione delle Strutture molto complesse, appartenenti a librerie condivise .so esterne, consiste nel fatto che, tentando di riprodurre nel progetto Gambas tali Strutture, il linguaggio non riesce sempre a gestire la coerenza dei necessari allineamenti tra alcuni particolari membri costituenti la Struttura medesima.
Ad ogni modo alcune Strutture complesse possono essere affrontate tentandone la lettura e la scrittura attraverso un Puntatore. All'interno dell'area puntata da detto Puntatore si scorrerà con i Memory Stream (per la lettura e/o scrittura), oppure semplicemente sommando o sottrando valori alla variabile Puntatore come segue:
p = p + n p = p - n
oppure con le funzioni
Inc p Dec p
e quindi dereferenziando con le apposite funzioni di dereferenziazione di un Puntatore previste da Gambas (solo per la lettura).
Indice
- 1 La questione dell'Allineamento dei membri della Struttura
- 1.1 Gestione se un membro è Array o una Matrice
- 1.2 Gestione se un membro fa riferimento ad una Union
- 1.3 Gestione se un membro fa riferimento ad altra Struttura o ad una Struttura annidata
- 1.4 Primo esempio
- 1.5 Secondo esempio
- 1.6 Determinazione definitiva della quantità di memoria occupata da una Struttura
La questione dell'Allineamento dei membri della Struttura
L'ostacolo maggiore è rappresentato dagli eventuali necessari allineamenti fra i membri, dei quali si dovrà tenere rigidamente conto durante la lettura e la scrittura nello spostamento lungo l'area puntata dal Puntatore.
In particolare, sorvolando sull'ovvia circostanza che i valori del primo membro della Struttura esterna sono leggibili e scrivibili a cominciare dal primo byte dell'area puntata dal Puntatore, v'è da precisare che per i valori appartenenti ai membri successivi al primo membro, il primo byte del valore di ogni membro (successivo al primo membro della Struttura) si trova al numero di indice (offset) uguale al valore che rappresenta la dimensione (quantità di byte occupati) del tipo di variabile contenente il valore medesimo, oppure uguale - se tale indice è già occupato da altro valore - al multiplo più prossimo al valore della dimensione predetta.
Spesso, se due membri vicini appartengono a tipi di valore diverso, si rende necessario l'allineamento della memoria occupata. L'allineamento sarà determinato dal membro fra i due di tipo maggiore, ossia che occupa ordinariamente la quantità più grande di memoria rispetto al tipo dell'altro membro.
Una regola, ad ogni modo, assoluta, di cui possiamo sempre tenere conto tranquillamente, è che un membro non comincerà mai all'interno dell'area di memoria della Strittura da un byte dispari, eccezion fatta per il tipo "char".
Vediamo un esempio con maggior dettaglio:
Poniamo il caso che la Struttura presente nella libreria condivisa dinamica esterna sia così composta:
struct STRUTTURA { char c; int i; char * p; short s; };
Laddove sostanzialmente abbiamo:
il primo membro di tipo char occupa 1 byte di memoria (come in Gambas il tipo Byte);
il secondo membro di tipo int occupa 4 byte di memoria (come in Gambas il tipo Integer);
il terzo membro di tipo char * (Puntatore) occupa 8 byte (nei sistemi a 64bit) di memoria (come in Gambas il tipo Pointer);
il quarto membro di tipo short occupa 2 byte di memoria (come in Gambas il tipo Short).
Tali membri sono dislocati all'interno dell'area di memoria occupata dalla loro Struttura secondi il seguente ragionamento e calcolo:
- il valore contenuto dal primo membro, ovviamente, ha inizio nell'area di memoria dal byte di indice 0 (il primo membro coincide sempre con l'indice zero).
Il primo membro occupa, come abbiamo visto solo un byte di memoria nell'area riservata, pertanto il prossimo byte da verificare è quello di indice 1.
- Il secondo membro, però, essendo un Intero (int) ha una dimensione di 4 byte (occupa, cioè, 4 byte di memoria per rappresentare il valore assegnatogli). Pertanto, si dovrà verificare se il numero dell'indice (offset) disponibile (abbiamo visto che ora è uguale a 1) coincide con il valore della dimensione del tipo int, oppure con un suo multiplo (8, 12, 16, 20, etc). Notiamo che 1 (l'indice attuale) non coincide con il 4 (dimensione di memoria occupata dal tipo int) né con un suo multiplo. Pertanto, sposteremo in avanti l'indice di un altro byte per la verifica. L'indice 1 resterà con valore zero ed entrando così a far parte dell'allineamento.
Ora verifichiamo il nuovo indice disponibile: 2, che come il precedente non corrisponde al numero 4 (ricordiamo che v'è da piazzare un Intero !) né ad un multiplo di 4. Quindi spostiamo ancora in avanti il puntatore interno dell'indice (offset), come fatto appena prima, e lasceremo a zero anche il terzo byte (ossia quello di indice 2 !), entrando così anch'esso a far parte dell'allineamento.
Ora verifichiamo il nuovo indice disponibile: 3, che come il precedente non corrisponde al numero 4 (ricordiamo che v'è da piazzare un Intero !) né ad un multiplo di 4. Quindi spostiamo ancora in avanti il puntatore interno dell'indice, e lasceremo a zero anche il quarto byte (ossia quello di indice 3), entrando così anch'esso a far parte dell'allineamento.
Ora verifichiamo il nuovo indice disponibile: 4. Questo numero corrisponde esattamente con il valore della dimensione di un Intero (int). Quindi il primo byte del valore assegnato a tale Intero sarà posto al byte di indice 4 (dunque al 5° byte), e occuperà ovviamente ben 4 byte, spostando così il puntatore interno al byte di indice 8 dell'area di memoria della Struttura esterna.
- Possiamo passare ad individuare il terzo membro, che essendo una variabile Puntatore (char *) occupa 8 byte (nei sistemi a 64bit). Esso occuperà (nei sistemi a 64bit) comunque 8 byte di memoria, qualunque sia la dimensione dell'area di memoria riservata, che nel codice sarà stata allocata con la funzione malloc(), alla quale esso punta. Dobbiamo, quindi, verificare se l'attuale numero dell'indice disponibile, al quale ci siamo sin'ora spostati, corrisponde a 8 o ad un multiplo di 8 (8, 16, 24, 32, etc).
Abbiamo che l'indice attuale è 8 (9° byte dell'area di memoria della Struttura esterna) che corrisponde esattamente con il valore della dimensione di un Puntatore. Pertanto il primo byte del valore contenuto dalla variabile Puntatore char * (ossia l'indirizzo di memoria puntata da questa variabile di tipo Puntatore) è posto al byte di indice 8 (9° byte), ed occuperà in totale ben 8 byte, spostando così il puntatore interno al byte di indice 16 dell'area di memoria della Struttura esterna.
- Possiamo passare ad individuare il quarto membro, che essendo una variabile Short (short) occupa 2 byte. Dobbiamo, quindi, verificare se l'attuale numero dell'indice disponibile, al quale ci siamo sin'ora spostati, corrisponde a 2 o ad un multiplo di 8 (4, 6, 8, 10, 12, 14, 16, etc).
Abbiamo che l'indice attuale è 16 (17° byte dell'area di memoria della Struttura esterna) che corrisponde ad un multiplo del valore della dimensione di uno short. Pertanto il primo byte del valore contenuto dalla variabile short è posto al byte di indice 16 (17° byte), ed occuperà in totale ben 2 byte.
La dimensione totale della Struttura raggiungerà il valore più prossimo pari ad un multiplo di 8 (dato che è presente un Puntatore fra i membri). In tal caso: 24. Quindi la Struttura "in quanto tale" occuperà complessivamente 24 byte di memoria.
Mostriamo di seguito un esempio di gestione della Struttura in linguaggio C appena descritta sopra. In particolare, per essa scriveremo un'apposita libreria dinamica e condivisa .so esterna, mediante la quale sarà utilizzata con un'applicazione Gambas principale la predetta Struttura in tutti i suoi membri sia scivendovi che leggendovi valori.
Private Extern Valorizza(stP As Pointer) As Pointer In "/tmp/libadhoc" Public Sub Main() Dim p, rit, rit2 As Pointer Dim bb As Byte[] = [1, 2, 3, 4] Dim st1, st2 As Stream Dim b As Byte Dim c As Short Dim i As Integer CreaSo() ' Per usare il "Puntatore", è necessario allocare un'area di memoria di dimensione pari alla quantità di memoria occupata dalla "Struttura" esterna in C da gestire: p = Alloc(24) ' Scriviamo dei valori dell'area di memoria allocata, che sarà passata successivamente alla funzione esterna per assegnare detti valori alla "Struttura" esterna da gestire: st1 = Memory p For Write Write #st1, 9 As Byte Seek #st1, 4 Write #st1, 9999 As Integer Write #st1, bb.Data As Pointer Write #st1, 99 As Short st1.Close ' Viene invocata la funzione esterna per l'assegnazione dei valori scritti nell'area di memoria allocata e puntata dal "Puntatore", e per leggere successivamente i dati dalla "Struttura" medesima: rit = Valorizza(p) ' La lettura dei dati presenti nell'area di memoria puntata dal "Puntatore" passato dalla funzione esterna, può avvenire con la dereferenziazione mediante i "Memory Stream"...: st = Memory rit For Read Read #st1, b Print "Valore del 1° membro (char): ";; b Seek #st1, 4 Read #st1, i Print "Valore del 2° membro (int): ";; i Read #st1, rit2 st2 = Memory rit2 For Read Read #st2, b Print "\nValore indice 0 del 3° membro (char *): ";; b Read #st2, b Print "Valore indice 1 del 3° membro (char *): ";; b Read #st2, b Print "Valore indice 2 del 3° membro (char *): ";; b Read #st2, b Print "Valore indice 3 del 3° membro (char *): ";; b st2.Close Read #st1, c Print "\nValore del 4° membro (short): ";; c ' ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ...oppure scorrendo all'interno del "Puntatore" e dereferenziando con le apposite funzioni di Gambas di dereferenziazione: rit = Valorizza(p) Print "Valore del 1° membro (char): ";; Byte@(rit) rit2 = rit + 4 Print "Valore del 2° membro (int): ";; Int@(rit2) rit2 = rit + 8 Print "\nValore indice 0 del 3° membro (char *): ";; Byte@(Pointer@(rit2)) Print "Valore indice 1 del 3° membro (char *): ";; Byte@(Pointer@(rit2) + 1) Print "Valore indice 2 del 3° membro (char *): ";; Byte@(Pointer@(rit2) + 2) Print "Valore indice 3 del 3° membro (char *): ";; Byte@(Pointer@(rit2) + 3) rit2 = rit + 16 Print "\nValore del 4° membro (short): ";; Short@(rit2) Free(p) End Private Procedure CreaSo() Dim s As String ' Impostiamo il codice sorgente C della futura libreria dinamica codivisa .so: s = "#include <stdlib.h>\n" & "#include <stdio.h>\n\n" & "struct STRUTTURA {\n" & "char c;\n" & "int i;\n" & "char * p;\n" & "short s;\n};\n\n" & "struct STRUTTURA Ra;\n\n" & "struct STRUTTURA * Valorizza(struct STRUTTURA *St) {\n" & "St->c *= 10;\n" & "St->i *= 10;\n" & "St->p[0] *= 10;\n" & "St->p[1] *= 10;\n" & "St->p[2] *= 10;\n" & "St->p[3] *= 10;\n" & "St->s *= 10;\n" & "Ra = *St; /* Assegna i dati della variabile St alla omogenea variabile Ra */\n" & "St = (struct STRUTTURA *) 0; /* Pulisce ed azzera i membri della Struttura */\n" & "return &Ra;\n}" File.Save("/tmp/libadhoc.c", s) ' Crea la libreria dinamica condivisa .so: Shell "gcc -o /tmp/libadhoc.so /tmp/libadhoc.c -shared -fPIC" Wait End
Gestione se un membro è Array o una Matrice
Se uno o più membri sono variabili vettoriali (Array) con il numero di elementi definito, allora si conta il numero degli elementi indicato nel vettore moltiplicato per la quantità di memoria occupata dal tipo dell'Array.
Esempio:
int i[4] --> 4 x 4 (ossia 4 elementi moltiplicato 4 byte di memoria occupati dal tipo int) = 16 byte complessivi
Così se ad esempio si ha un Puntatore:
int *[4] --> 4 * 8 (ossia 4 elementi moltiplicato 8 byte di memoria occupati dal tipo Puntatore) = 32 byte complessivi
Calcolo analogo in caso di Array multidimensionale (Matrice): si moltiplicheranno fra loro i valori che indicano gli elementi di ciascuna dimensione della Matrice, il risultato sarà in fine moltiplicato per per la quantità di memoria occupata dal tipo della Matrice.
Esempio:
int i[4][3] --> (4 x 3) x 4 (ossia 4 elementi della prima dimensione moltiplicato 3 elementi della seconda dimensione, moltiplicato i 4 byte di memoria occupati dal tipo int) = 48 byte complessivi
In modo simile se la Matrice ha più di due dimensioni.
Mostriamo di seguito un esempio, in cui il codice sorgente in C della libreria dinamica condivisa .so esterna è il seguente:
#include <stdlib.h> struct AAA { char c; short s; int i; char * p[5]; char * p1, * p2; int n; }; struct AAA a; struct AAA * Estrae_Valori() { a.c = 0x0F; a.s = 0x0400; a.i = 0x00444444; a.p[0] = (char *) malloc(4); a.p[1] = (char *) malloc(4); a.p[2] = (char *) malloc(4); a.p[3] = (char *) malloc(4); a.p[4] = (char *) malloc(4); a.p[0][0] = 0x01; a.p[0][1] = 0x02; a.p[0][2] = 0x03; a.p[0][3] = 0x04; a.p[1][0] = 11; a.p[1][1] = 11; a.p[1][2] = 11; a.p[1][3] = 11; a.p[2][0] = 22; a.p[2][1] = 22; a.p[2][2] = 22; a.p[2][3] = 22; a.p[3][0] = 33; a.p[3][1] = 33; a.p[3][2] = 33; a.p[3][3] = 33; a.p[4][0] = 44; a.p[4][1] = 44; a.p[4][2] = 44; a.p[4][3] = 44; a.p1 = (char *) malloc(4); a.p1[0] = 99; a.p1[1] = 99; a.p1[2] = 99; a.p1[3] = 99; a.p2 = "efgh"; a.n = 10000; return &a; }
che ritorna alla funzione chiamante di Gambas l'indirizzo di memoria della sua Struttura.
Nel codice Gambas, che segue, si andrà a leggere da un Puntatore, che ha raccolto l'indirizzo di memoria della Struttura esterna ritornata dalla libreria esterna, e con i Memory Stream i valori dei vari membri della predetta Struttura
Si ponga particolare attenzione al calcolo per l'eventuale necessaroi allineamento dei membri.
Private Extern Estrae_Valori() As Pointer In "/tmp/stru" Public Sub Main() Dim p, po, p1, p2 As Pointer Dim st, re, s1, s2 As Stream Dim b As Byte Dim j As Short Dim t As String ' Generiamo la libreria esterna, scritta in C, contenente la "Struttura": Shell "gcc -o /tmp/stru.so " & Application.Path &/ "stru.c -shared -fPIC" Wait p = Estrae_Valori() st = Memory p For Read Print Read #st As Byte ' Legge il 1° membro (char) che occupa 1 byte Seek #st, 2 ' Ci spostiamo nel rispetto dell'allineamento dei membri Print Read #st As Short ' Legge il 2° membro (short) che occupa 2 byte Print Read #st As Integer ' Legge il 3° membro (int) che occupa 4 byte Read #st, po ' Legge il 3° membro: il puntatore (che occupa 8 byte) della prima dimensione di char * p[5] re = Memory po For Read ' Andiamo a dereferenziare il "Puntatore" per leggere nell'area di memoria da esso puntata For j = 0 To 3 Read #re, b Print j, b Next Seek #re, 32 ' Per ciascuna dimensione si salta di 32 byte avanti For j = Seek(re) To Seek(re) + 3 Read #re, b Print j, b Next Seek #re, 64 ' Per ciascuna dimensione si salta di 32 byte avanti For j = Seek(re) To Seek(re) + 3 Read #re, b Print j, b Next Seek #re, 96 ' Per ciascuna dimensione si salta di 32 byte avanti For j = Seek(re) To Seek(re) + 3 Read #re, b Print j, b Next Seek #re, 128 ' Per ciascuna dimensione si salta di 32 byte avanti For j = Seek(re) To Seek(re) + 3 Read #re, b Print j, b Next re.Close Seek #st, Seek(st) + (SizeOf(gb.Pointer) * 4) ' Si moltiplica per l'indice massimo di char * p[n] Read #st, p1 ' Legge il 5° membro: il puntatore (che occupa 8 byte) della prima dimensione di char * p1 s1 = Memory p1 For Read ' Andiamo a dereferenziare il "Puntatore" per leggere nell'area di memoria da esso puntata For j = 0 To 3 Read #s1, b Print j, b Next s1.Close Read #st, p2 ' Legge il 6° membro: il puntatore (che occupa 8 byte) della prima dimensione di char * p2 s2 = Memory p2 For Read ' Andiamo a dereferenziare il "Puntatore" per leggere nell'area di memoria da esso puntata Read #s2, t Print t s2.Close Print Read #st As Integer ' Legge il 7° membro (int) che occupa 4 byte st.Close End
Gestione se un membro fa riferimento ad una Union
Se uno o più membri della Struttura fanno riferimento ad una Union, allora si calcola solo il membro della Union che occupa la quantità maggiore di memoria rispetto agli altri membri.
Se due membri contingui della Struttura sono due Union, e se l'ultimo membro della prima delle due Union è di tipo comunque diverso dal tipo dal tipo del primo membro della seconda Union, va effettuato l'allineamento fra le due Union secondo i consueti criteri.
Gestione se un membro fa riferimento ad altra Struttura o ad una Struttura annidata
Se uno o più membri della Struttura fanno riferimento ad un'altra Struttura, o ad una Struttura definita all'interno della Struttura principale (e quindi fa riferimento in tal caso ad una Struttura annidata), si effettua il calcolo - nelle modalità consuete - della quantità di memoria occupata complessivamente dai membri della Struttura esterna o dalla Struttura annidata. Se il membro è dichiarato come Puntatore, allora esso occuperà semplicemente 8 byte, ossia la quantità di byte occupati ordinariamente dalla variabile di tipo Puntatore.
Nel caso il membro, che si riferisce ad altra Struttura secondaria o ad una Struttura annidata, sia un Array con numero definito di elementi, la quantità che occupa la Struttura secondaria o la Struttura annidata va moltiplicata per il numero espresso degli elementi dellArray.
Mostriamo due codici esemplificativi.
Primo esempio
Nel seguente codice la Struttura secondaria, innestata in quella principale, è dichiarata con una ordinaria varibile di tipo Struttura:
#include <stdio.h> struct STRUTTURA { char c; short s; int i; struct Innesto { short Is; long Il; } In; } St; int main() { printf("%ld\n", sizeof(St)); return (0); }
In tal caso la dimensione complessiva della Struttura principale (che nell'esempio abbiamo denominato con l'identificativo: STRUTTURA) è pari a 24 byte, così distribuiti:
- 1 byte per il primo membro (essendo di tipo char);
- 1 byte per il necessario allineamento;
- 2 byte per il secondo membro (essendo di tipo short);
- 4 byte per il terzo membro (essendo di tipo int);
- 2 byte per il primo membro della Struttura innestata (essendo di tipo short);
- 2 byte per il necessario allineamento;
- 8 byte per il secondo membro della Struttura innestata (essendo di tipo long).
Secondo esempio
In questo secondo codice, invece, la Struttura secondaria, innestata in quella principale, è dichiarata mediante una varibile di tipo Puntatore:
#include <stdio.h> struct STRUTTURA { char c; short s; int i; struct Innesto { short Is; long Il; } *In; } St; int main() { printf("%ld\n", sizeof(St)); return (0); }
In tal caso la dimensione complessiva della Struttura principale è pari a 16 byte, così distribuiti:
- 1 byte per il primo membro (essendo di tipo char);
- 1 byte per il necessario allineamento;
- 2 byte per il secondo membro (essendo di tipo short);
- 4 byte per il terzo membro (essendo di tipo int);
- 8 byte (nei sistemi a 64bit) per il quarto membro (essendo una variabile di tipo Puntatore).
Determinazione definitiva della quantità di memoria occupata da una Struttura
Per la determinazione finale e complessiva della quantità di memoria occupata dalla Struttura da effettuare dopo l'ultimo membro, può risultare necessario individuare la quantità di memoria residua aggiuntiva, da occupare, tale da raggiungere il valore del byte multiplo immediatamente successivo (il più prossimo) della quantità di memoria occupata dal tipo più grande (in quantità di memoria occupata) fra quelli utilizzati come membri nella Struttura medesima.
Vediamo un esempio:
struct STRUTTURA_1 { long l; /* Questo è il tipo "maggiore" rispetto agli altri, perché occupa più memoria (8 byte) rispetto agli altri. */ char c1; short sh; char c2; int i; };
Ebbene, la Struttura sembrerebbe occupare complessivamente 20 byte. Però il tipo di valore che occupa maggiore memoria è il tipo long (8 byte) rispetto agli altri. Il valore 20, che sembrerebbe essere la quantità di memoria occupata dalla Struttura non è esatto, poiché il valore 20 non è un multiplo di sizeof(long) = 8. Pertanto, per sapere quanta memoria in questo caso occupa complessivamente e correttamente la Struttura, si dovrà tenere conto dell'allineamento sino al valore successivo più vicino in ordine crescente a 20, ossia 24. Quindi la Struttura in esempio occupa 24 byte di memoria.
Vediamo un altro esempio simile al precedente:
struct STRUTTURA_2 { long l; /* Questo è il tipo "maggiore" rispetto agli altri, perché occupa più memoria (8 byte) rispetto agli altri. */ char c1; short sh; char c2; int i1; int i2; int i3; };
La Struttura sembra occupare 28 byte; ma 28 non è un multiplo del valore della quantità di memoria occupata dal tipo più grande presente nella Struttura: il long. Pertanto, l'allineamento porterà ad occupare complessivamente una quantità di memoria aggiuntiva terminale sino al valore multiplo più prossimo in ordine crescente al 28, ossia 32. Quindi la Struttura in esempio occupa 32 byte di memoria.
Altro esempio:
struct STRUTTURA_3 { char c1; int i; /* Questo è il tipo "maggiore" rispetto agli altri, perché occupa più memoria (4 byte) rispetto agli altri. */ short sh; char c2; };
La Struttura sembra occupare 11 byte; ma 11 non è un multiplo del valore della quantità di memoria occupata dal tipo più grande presente nella Struttura: il int. Pertanto, l'allineamento porterà ad occupare complessivamente una quantità di memoria aggiuntiva terminale sino al valore multiplo più prossimo in ordine crescente al 11, ossia 12. Quindi la Struttura in esempio occupa 12 byte di memoria.