Barninga Z
a- a+

Gli operatori: cast e conversioni di tipo

Cast e conversioni di tipo 

In una espressione è sempre possibile avere operandi di tipo diverso. Non è poi così strano dividere, ad esempio, un numero in virgola mobile per un numero intero, oppure, anche se a prima vista può sembrare meno ovvio, moltiplicare un intero per un carattere. In ogni caso, comunque, il risultato dell'operazione deve essere di un unico tipo, di volta in volta ben determinato: in tali casi è sempre necessario, perciò, procedere a conversioni di tipo su almeno uno degli operandi coinvolti. 

Il C, al riguardo, fissa un ordine "gerarchico" dei tipi di dato intrinseci, e stabilisce due semplici regole che consentono di conoscere sempre a priori come verranno effettuate le necessarie conversioni. 

L'ordine gerachico dei tipi, decrescente da sinistra a destra, è il seguente: 

    long double > double > float > long > int > short > char

Ne risulta che ogni tipo è di "grado" superiore ad ogni altro tipo elencato alla sua destra e di grado inferiore a quello dei tipi elencati alla sua sinistra. Sulla scorta di tale gerarchia, la prima regola stabilisce che nelle espressioni che non coinvolgono operatori di assegnamento, in ogni coppia di operandi l'operando di grado inferiore è convertito nel tipo dell'operando avente grado superiore. Così, ad esempio, in una operazione di confronto tra un float e un long, quest'ultimo è convertito in float prima che sia effettuato il confronto. 

La seconda regola riguarda invece le operazioni di assegnamento: l'espressione a destra dell'operatore di assegnamento è sempre convertita nel tipo della variabile che si trova a sinistra del medesimo, indipendentemente dal livello gerarchico dei dati coinvolti. 

Naturalmente le due regole possono trovare contemporanea applicazione quando ad una variabile sia assegnato il risultato di un'espressione che coinvolge operandi di tipi differenti: 

int iVar;
    long lVar;
    float fVar;
    char cVar;

    ....
    iVar = fVar + lVar * cVar;

Nell'esempio, l'operatore di moltiplicazione ha precedenza rispetto a quello di somma, perciò viene dapprima calcolato il prodotto di lVar per cVar, dopo avere convertito cVar in long. Il valore ottenuto è poi sommato a quello contenuto in fVar, ma solo dopo averlo convertito in float. Il risultato, infine, viene convertito in int ed assegnato a iVar

Si tenga presente che le conversioni effettuate in modo automatico dal compilatore C implicano un troncamento della parte più significativa del valore convertito quando esso viene "degradato" ad un livello inferiore, ed un'aggiunta di bit nulli quando è "promosso" ad un tipo di livello superiore. Nel secondo caso il valore originario del dato può sempre venire conservato; nel primo, al contrario, esiste il rischio di perdere una parte (la più significativa) del valore convertito. 

L'affermazione risulta palese se si pensa, ad esempio, al caso di una conversione da int a long ed una viceversa: consideriamo due variabili, la prima di tipo int (16 bit) e la seconda di tipo long (32 bit), contenenti, rispettivamente, i valori 5027 (che in codice binario è 0001001110100011) e 2573945 (in binario 00000000001001110100011001111001): la conversione della prima in long implica l'aggiunta di 16 bit nulli alla sinistra di quelli "originali". Lo spazio occupato è ora di 32 bit, ma il valore di partenza non viene modificato. Nel convertire il long in int, al contrario, vengono eliminati i 16 bit più significativi (quelli più a sinistra): i 16 bit rimanenti sono 0100011001111001, che equivalgono, in notazione decimale, a 18041

Conversioni di tipo automatiche sono effettuate anche quando il tipo dei parametri passati ad una funzione non corrisponde al tipo dei parametri che la funzione "desidera". Inoltre, in questo caso, i char sono sempre convertiti in int, anche se la funzione si aspetta di ricevere proprio un char[3]. Va anche sottolineato che il compilatore, in genere, emette un messaggio di warning quando la conversione di tipo generata in modo automatico comporta il rischio di perdere una parte del valore coinvolto. 

Vi sono però spesso situazioni in cui il compilatore non è in grado di effettuare la conversione in modo automatico; ad esempio quando sono coinvolti tipi di dato non intrinseci, definiti dal programmatore (quali strutture, campi di bit, etc.). Altre volte, invece, si desidera semplicemente esplicitare una conversione che il compilatore potrebbe risolvere da sé, al fine di rendere più chiaro il codice o per evitare il warning ad essa correlato. 

In tutti questi casi si può ricorrere all'operatore di cast, il quale forza un qualunque valore ad appartenere ad un certo tipo. La notazione è la seguente: 

(tipo)espressione

dove tipo può essere una qualsiasi delle parole chiave del C utilizzate nelle dichiarazioni di tipo ed espressione dev'essere una qualsiasi espressione sintatticamente corretta. Ad esempio: 

int iVar;

    iVar = (int)3.14159;

La conversione illustrata può essere automaticamente eseguita dal compilatore, ma l'esplicitarla mediante l'operatore di cast incrementa la chiarezza del codice ed evita il messaggio di warning. Un altro caso in cui si effettua spesso il cast è l'inizializzazione di un puntatore far o huge con una costante a 32 bit: 

char far *colVbuf = (char far *)0xB8000000L;   
// ptr buffer video testo col.

La conversione automatica, in questo caso, non comporterebbe alcun errore, dal momento che la costante assegnata al puntatore è un dato a 32 bit, esattamente come il puntatore stesso: il compilatore emetterebbe però una segnalazione di warning, per evidenziare al programmatore che un dato di tipo long viene assegnato ad un puntatore far a carattere: una questione di forma, insomma. Di fatto la costante potrebbe essere scritta anche senza la "L" che ne indica inequivocabilmente la natura long, ma in quel caso il compilatore segnalerebbe, con un altro warning, che vi è una costante che, per il valore espresso, deve essere considerata long senza che ciò sia stato esplicitamente richiesto. 

Più significativo può essere l'esempio seguente: 

struct FARPTR {
    unsigned offset;
    unsigned segment;
};

    ....
    char far *cFptr;
    struct FARPTR fPtr;
    ....
    (char far *)fPtr = cFptr;

In questo caso la struttura di tipo FARPTR è utilizzata per accedere separatamente alla parte segmento e alla parte offset di un puntatore far. In pratica, il valore contenuto nel puntatore far è copiato nell'area di memoria occupata dalla struttura: si tratta di un'operazione che potrebbe provocare l'emissione di un messaggio di errore e l'interruzione della compilazione. La presenza dell'operatore di cast tranquillizza il compilatore; dal canto nostro sappiamo che struttura e puntatore occupano entrambi 32 bit, perciò siamo tranquilli a nostra volta. 

 



Ti potrebbe interessare anche

commenta la notizia

C'è 1 commento
Redazione
Condividi le tue opinioni su questo articolo!