Barninga Z
a- a+

I Puntatori far e huge

PUNTATORI 

Puntatori far e huge 

Le considerazioni sin qui espresse, però, aprono lavia ad alcuni approfondimenti. In primo luogo, vasottolineato ancora una volta che numPtr occupa 16bit di memoria, cioè 2 byte, proprio come qualsiasiunsigned int. E ciò è valido anche seil tipo di numero, la variabile puntata, è ilfloat, che ne occupa 4. In altre parole, unpuntatore occupa sempre lo spazio necessario a contenerel'indirizzo del dato puntato, e non il tipo di dato;tutti i puntatori come numPtr, dunque, occupano 2byte, indipendentemente che il tipo di dato puntato sia unint, piuttosto che un float, o undouble... Una semplice verifica empirica puòessere effettuata con l'aiuto dell'operatoresizeof(). 

int unIntero;    long unLongInt;    float unFloating;    double unDoublePrec;    int *intPtr;    long *longPtr;    float *floatPtr;    double *doublePtr;    printf("intPtr:    %d bytes (%d)" ,sizeof(intPtr),sizeof(int *));    printf("longPtr:   %d bytes (%d)" ,sizeof(longPtr),sizeof(long *));    printf("floatPtr:  %d bytes (%d)" ,sizeof(floatPtr),sizeof(float *));    printf("doublePtr: %d bytes (%d)" ,sizeof(doublePtr),sizeof(double *));

Tutte le printf() visualizzano due volte il valore2, che è appunto la dimensione in byte di ungenerico puntatore. L'esempio mostra, tra l'altro,come sizeof() possa essere applicato sia al tipo didato che al nome di una variabile (in questo caso deipuntatori); se ne trae, infine, che il tipo di un puntatoreè dato dal tipo di dato puntato, seguitodall'asterisco. 

Tutti i puntatori come numPtr, dunque, gestisconoun offset da un punto di partenza automaticamente fissato dalsistema operativo in base alle caratteristiche del fileeseguibile. E' possibile in C, allora, gestire indirizzilineari, o quanto meno comprensivi di segmento ed offset? Larisposta è sì. Esistono due parole chiave,dette modificatori di tipo, che consentono di dichiararepuntatori speciali, in grado di gestire sia la parte segmentoche la parte offset di un indirizzo di memoria: si tratta difar e huge

double far *numFarPtr;

La riga di esempio dichiara un puntatore far a undato di tipo double. Per effetto del modificatorefar, numFarPtr è un puntatore assaidifferente dal numPtr degli esempi precedenti: essooccupa 32 bit di memoria, cioè 2 word, ed èpertanto equivalente ad un long int. Di conseguenzanumFarPtr è in grado di esprimere tanto laparte offset di un indirizzo (nei 2 byte meno significativi),quanto la parte segmento (nei  2 byte piùsignificativi[12]). La parte segmento è utilizzatadalla CPU per caricare l'opportuno registro di segmento,mentre la parte offset è gestita come al solito: intal modo un puntatore far può esprimere unindirizzo completo del tipo segmento:offset e indirizzaredati che si trovano al di fuori dell'area dati assegnatadal sistema operativo al programma. 

Ad esempio, se si desidera che un puntatore referenzil'indirizzo 596A:074B, lo si puòdichiarare ed inizializzare come segue: 

double far *numFarPtr = 0x596A074B;

Per visualizzare il contenuto di un puntatore farcon printf() si può utilizzare unformattatore speciale: 

printf("numFarPtr = %Fp" ,numFarPtr);

Il formattatore %Fp forza printf() avisualizzare il contenuto di un puntatore farproprio come segmento ed offset, separati dai duepunti: 

numFarPtr = 596A:074B

è l'output prodotto dalla riga di codice appenariportata. 

Abbiamo appena detto che un puntatore farrappresenta un indirizzo seg:off. E' bene... ripeterloqui, sottolineando che quell'indirizzo, in quantoseg:off, non è un indirizzo lineare. Parte segmento eparte offset sono, per così dire, indipendenti, nelsenso che la prima è considerata costante, e laseconda variabile. Che significa? la riga 

char far *vPtr = 0xB8000000;

dichiara un puntatore far a carattere e loinizializza all'indirizzo B800:0000; la parteoffset è nulla, perciò il puntatore indirizzail primo byte dell'area che ha inizio all'indirizzolineare B8000 (a 20 bit). Il secondo byte ha offsetpari a 1, perciò può essereindirizzato incrementando di 1 il puntatore,portandolo al valore 0xB8000001. Incrementandoancora il puntatore, esso assume valore 0xB8000002 epunta al terzo byte. Sommando ancora 1 al puntatore,e poi ancora 1, e poi ancora... si giunge ad unvalore particolare, 0xB800FFFF, corrispondenteall'indirizzo B800:FFFF, che è proprioquello del byte avente offset 65535 rispettoall'inizio dell'area. Esso è l'ultimo byteindirizzabile mediante un comune  puntatorenear[13]. Che accade se si incrementa ancoravPtr? Contrariamente a quanto ci si potrebbeattendere, la parte offset si riazzera senza che alcun"riporto" venga sommato alla parte segmento.Insomma, il puntatore si "riavvolge" all'iniziodell'area individuata dall'indirizzo linearerappresentato dalla parte segmento con uno 0 allapropria destra (che serve a costruire l'indirizzo a 20bit). Ora si comprende meglio (speriamo!) che cosa si intendeper parte segmento e parte offset separate: esse sonoutilizzate proprio per caricare due distinti registri dellaCPU e pertanto sono considerate indipendenti l'unadall'altra, così come lo sono tra loro tutti iregistri del microprocessore. 

Tutto ciò ha un'implicazione estremamenteimportante: con un puntatore far è possibileindirizzare un dato situato ad un qualunque indirizzo nellamemoria disponibile entro il primo Mb, ma non èpossibile "scostarsi" dall'indirizzo lineareespresso dalla parte segmento oltre i 64Kb. Per fare unesempio pratico, se si intende utilizzare un puntatorefar per gestire una tabella, la dimensionecomplessiva di questa non deve eccedere i 64Kb. 

Tale limitazione è superata tramite il modificatorehuge, che consente di avere puntatori in grado diindirizzare linearmente tutta la memoria disponibile (sempreentro il primo Mb). La dichiarazione di un puntatorehuge non presenta particolarità: 

int huge *iHptr;

Il segreto dei puntatori huge consiste in alcuneistruzioni assembler che il compilatore introduce disoppiatto nei programmi tutte le volte che il valore delpuntatore viene modificato o utilizzato, e che ne effettuanola normalizzazione. Con tale termine si indica un semplicecalcolo che consente di esprimere l'indirizzo seg:offcome rappresentazione di un indirizzo lineare: in modo,cioè, che la parte offset sia variabile unicamente da0 a 15 (F esadecimale) ed iriporti siano sommati alla parte segmento. In pratica sitratta di sommare alla parte segmento i 12 bit piùsignificativi della parte offset. Riprendiamo l'esempioprecedente, utilizzando questa volta un puntatorehuge

char huge *vhugePtr = 0xB8000000;

L'inizializzazione del puntatore huge, come sivede, è identica a quella del puntatore far.Incrementando di 1 il puntatore si ottiene il valore0xB8000001, come nel caso precedente. Sommandoancora 1 si ha 0xB8000002, e poi0xB8000003, e così via. Sin qui, nulla dinuovo. Al quindicesimo incremento il puntatore vale0xB800000F, come nel caso del puntatorefar

Ma al sedicesimo incremento si manifesta la differenza: ilpuntatore far assume valore 0xB8000010,mentre il puntatore huge vale 0xB8010000:la parte segmento si è azzerata ed il 16sottratto ad essa ha prodotto  un riporto[14] cheè andato ad incrementare di 1 la partesegmento. Al trentunesimo incremento il puntatorefar vale 0xB800001F, mentre quellohuge è 0xB801000F. Al trentaduesimoincremento il puntatore far diventa0xB8000020, mentre quello huge vale0xB8020000

Il meccanismo dovrebbe essere ormai chiaro, così comeil fatto che le prime 3 cifre della parte offset di unpuntatore huge sono sempre 3 zeri. Fingiamo per unattimo di non vederli: la parte segmento e la quarta cifradella parte offset rappresentano proprio un indirizzo linearea 20 bit. 

La normalizzazione effettuata dal compilatore consente digestire indirizzi lineari pur caricando in modo indipendenteparte segmento e parte offset in registri di segmento e,rispettivamente, di offset della CPU; in tal modo, con unpuntatore huge non vi sono limiti néall'indirizzo di partenza, né alla quantitàdi memoria indirizzabile a partire da quell'indirizzo.Naturalmente ciò ha un prezzo: una piccola perdita diefficienza del codice eseguibile, introdotta dallanecessità di eseguire la routine di normalizzazioneprima di utilizzare il valore del puntatore. 

Ancora una precisazione: nelle dichiarazioni multiple dipuntatori far e huge, il modificatore deveessere ripetuto per ogni puntatore dichiarato, analogamente aquanto occorre per l'operatore di indirezione.L'omissione del modificatore determina la dichiarazionedi un puntatore "offset" a 16 bit. 

long *lptr, far *lFptr, lvar, huge *lHptr;

Nell'esempio sono dichiarati, nell'ordine, ilpuntatore a long a 16 bit lptr, ilpuntatore far a long lFptr, lavariabile long lvar e il puntatorehuge a long lHptr

E' forse il caso di sottolineare ancora che ladichiarazione di un puntatore riserva spazio in memoriaesclusivamente per il puntatore stesso, e non per unavariabile del tipo di dato indirizzato. Ad esempio, ladichiarazione 

long double far *dFptr;

alloca, cioè riserva, 32 bit di RAM che potrannoessere utilizzate per contenere l'indirizzo di unlong double, i cui 80 bit dovranno essere allocaticon  un'operazione a parte[15]. 

Tanto per confondere un poco le idee, occorre precisare unultimo particolare. I sorgenti C possono essere compilati,tramite particolari opzioni riconosciute dal compilatore, inmodo da applicare differenti criteri di default alla gestionedei puntatori. In particolare, vi sono modalità dicompilazione che trattano tutti i puntatori come variabili a32 bit, eccetto quelli esplicitamente dichiaratinear. Ne riparleremo descrivendo i modelli dimemoria. 

Per il momento è il caso di accennare a tre macro,definite in DOS.H, che agevolano in molti casi lamanipolazione dei puntatori a 32 bit, siano essi faro huge: si tratta di MK_FP(), che"costruisce" un puntatore a 32 bit dati un segmentoed un offset entrambi a 16 bit, di FP_SEG(), cheestrae da un puntatore a 32 bit i 16 bit esprimenti la partesegmento e di FP_OFF(), che estrae i 16 bitesprimenti l'offset. Vediamole al lavoro: 

 

#include    ....    unsigned farPtrSeg;    unsigned farPtrOff;    char far *farPtr;    ....    farPtr = (char far *)MK_FP(0xB800,0);  // farPtr punta a B800:0000    farPtrSeg = FP_SEG(farPtr);    // farPtrSeg contiene 0xB800    farPtrOff = FP_OFF(farPtr);    // farPtrOff contiene 0

Le macro testè descritte consentono di effettuarefacilmente la normalizzzione di un puntatore, cioètrasformare l'indirizzo in esso contenuto in modo taleche la parte offset non sia superiore a 0Fh

char far *cfPtr;    char huge *chPtr;    ....    chPtr = (char huge *)(((long)FP_SEG(cfPtr)) << 16)+  (((long)(FP_OFF(cfPtr) >> 4)) << 16)+(FP_OFF(cfPtr) & 0xF);

Come si vede, dalla parte offset sono scartati i 4 bit menosignificativi: i 12 bit più significativi sono sommatial segmento; dalla parte offset sono poi scartati i 12 bitpiù significativi e i 4 bit restanti sono sommati alpuntatore. Il significato degli operatori di shift<< e >> e dell'operatore& (che in questo caso non ha il significatodi  address of, ma di   and subit) è descritto più avanti. 

L'indirizzo lineare corrispondente all'indirizzosegmentato espresso da un puntatore huge puòessere ricavato come segue: 

char huge *chPtr;    long linAddr;    ....    linAddr = ((((((long)FP_SEG(chPtr)) << 16)+(FP_OFF(chPtr) << 12)) >> 12) &  0xFFFFFL);

Per applicare tale algoritmo ad un puntatore farè necessario che questo sia dapprima normalizzato comedescritto in precedenza. 

E' facile notare che due puntatori far possonoreferenziare il medesimo indirizzo pur contenendo valori a 32bit differenti, mentre ciò non si verifica con ipuntatori normalizzati, nei quali segmento e offset sonosempre gestiti in modo univoco: ne segue che solamente iconfronti tra puntatori huge (o normalizzati)garantiscono risultati corretti. 

 



Ti potrebbe interessare anche

commenta la notizia