Zoom Icon

Classi di memorizzazione pt2

From UIC Archive

Classi di memorizzazione (parte 2)

Contents


Classi di memorizzazione pt2
Author: anonymous
Email: [email protected]
Website:
Date: 19/09/2008 (dd/mm/yyyy)
Level: Some skills are required
Language: Italian Flag Italian.gif
Comments: Non credo di voler lasciare commenti



Ci sono cose del C che, evidentemente, mettono in difficoltà molta più gente di quanta dovrebbero. Una di queste sono i puntatori e, per non parlare dei puntatori a funzione, saltiamo alle Union. Beh, in questo momento, sto scrivendo, ho iniziato questa lezione ma non è una situazione standard in cui qualcuno scrive una lezione sulle union; da poco siamo nel giorno 13 agosto 2008, sono molto annoiato e prima riflettevo sul come mai alcune persone non capiscono a cosa servono queste robe per strutturare i dati in C. Non riesco tutt’ora a immaginare se questa guida sarà di dimensioni bibliche (vedi le nove pagine per patchare 1 byte :D) oppure se sarà così concisa da riuscire a spiegare la cosa con qualche esempi etto azzeccato. A ogni modo, iniziamo!



Essay

“Union”. Se devo essere sincero, immagino soltanto il perché abbiano questo nome. Intuitivamente, queste classi di memorizzazione servono per fornire “punti di vista” differenti da cui è possibile visualizzare o in generale accedere a UN certo dato, di un certo tipo. La loro costruzione è del tutto simile a quella delle struct ma, ovviamente, si cambia la parolina chiave. Discorsivamente mi trovo un po’ in difficoltà, quindi passiamo subito alla sintassi e alle mie adorate figurine. L’esempio più “operativo” per parlare di una union, è quello dell’indirizzo ipv4. Per chi non sapesse di cosa si tratta (consiglierei di spararsi, veramente), questo è una sequenza di quattro numeri interi, compresi tra 0 e 255 (con le dovute regole di consistenza e validità), separati da un punto (.), che identificano un’entità all’interno di una rete IP, nella sua attuale(??) versione 4. Un esempio di indirizzo IP potrebbe essere

212.56.3.23

Cosa c’azzecca con le union? Nulla.. però, molto spesso, nei sistemi informatici, un indirizzo ipv4, invece che essere utilizzato nella sua forma leggibile (detta anche decimal-dotted-format, DDF), viene rappresentato sottoforma di un intero a 32 bit. Se fate caso, infatti, dato che ogni numero ha l’ampiezza di 1 byte (in [0, 255]) e che ci sono esattamente quattro interi, tralasciando i tre punti (.) che non sono una variabile di rappresentazione, sappiamo che ogni ip è grande 4 byte = 32 bit. Una struct per rappresentare un ipv4 potrebbe essere la seguente: struct ipv4 { BYTE v4; BYTE v3; BYTE v2; BYTE v1; }; con BYTE opportunamente definito. Apro una piccola parentesi: nella struct, i campi per l’indirizzo IP sono dichiarati in ordine decrescente di indice per via di una convenzione di rappresentazione numerica adottata dai calcolatori intel (ovvero dal calcolatore sul quale sto scrivendo questa guida). Questa lezione non deve e non vuole spiegare l’argomento. Per adesso,fidatevi quando vi dico che gli interi vengono rappresentati scrivendo i byte che li compongono da destra verso sinistra. Tralasciamo un attimo la struct e passiamo dunque a un altro problema: rappresentiamo “a mano” il succitato indirizzo ip sotto forma di intero a 32 bit. Prima di tutto, convertiamo in binario (pad8) le quattro cifre che compongono l’indirizzo:

212 = 11010100
56 = 00111000
3 = 00000011
23 = 00010111

Dunque concateniamo le loro rappresentazioni in base 2 (pad8) per ottenere un numero a 32 bit in base 2 (pad32):

11010100 00111000 00000011 00010111

Copiamolo e incolliamolo nella nostra brava calcolatrice e clicchiamo sul pulsantino per la conversione in base 10, per ottenere il numero 3.560.440.599. 3 miliardi e mezzo, ovvero, una roba che non sembra significare assolutamente nulla se messa in relazione all’indirizzo ip che rappresenta. Adesso, pensate ad un codice C fatto in questo modo: struct ipv4 /* rappresentazione intel */ { BYTE v4; BYTE v3; BYTE v2; BYTE v1; };


int main(int argc, char** argv) { unsigned int iip = 3560440599; /* numero ottenuto con la calcolatrice */ struct ipv4 *sip = (struct ipv4 *) &iip; printf("%d.%d.%d.%d\n", sip->v1, sip->v2, sip->v3, sip->v4); system("pause"); return 0; }
E provate a immaginare che cosa stampa su video:

Union01.jpg

Vediamo: per prima cosa, ho dichiarato uno stupidissimo intero senza segno; poi, con un cast, ho chiesto al compilatore di farmelo “vedere” sotto forma di struct. In parole povere, ho chiesto al C di ignorare di che tipo fosse iip per farmi accedere ad esso come se fosse di tutt’altro tipo. E’ un semplice lavoro di casting. Sì – dice il popolo – ma le union? Beh.. in sostanza, quello che ho fatto è ciò che, implicitamente fa una union. Ovvero, tutto questo lavoro, si può fare senza alcun cast, scrivendo questo: struct ipv4 /* rappresentazione intel */ { BYTE v4; BYTE v3; BYTE v2; BYTE v1; };

union uipv4 { unsigned int iip; struct ipv4 sip; };


int main(int argc, char **argv) { union uipv4 miaunion;

miaunion.iip = 3560440599; printf("%d.%d.%d.%d\n", miaunion.sip.v1, miaunion.sip.v2, miaunion.sip.v3, miaunion.sip.v4);

system("pause"); return 0; } Il risultato è praticamente identico a quello del programma più in alto. In sostanza, nella dichiarazione della union, abbiamo scritto che vogliamo un dato che può essere visto sia come un intero senza segno, sia come una struttura ipv4.

Differenze con una struct

Questa è una domanda che sento spesso ma che, sinceramente, non so cosa significhi. Una struct e una union non sono oggetti comparabili! Proverò a rispondere, giusto per soddisfare il desiderio del lettore di avere una risposta.. A prima vista, direi che la differenza sostanziale è nella dimensione della union. Prendete uipv4 e pensatela come se fosse una struct. Il suo peso sarebbe dato dalla somma

sizeof(iip) + sizeof(sip)

proprio perché, nella struct, quelli sarebbero due campi differenti, che puntano a zone distinte in memoria. Nella union, entrambi i campi riferiscono la stessa zona di memoria; per questo il compilatore deve assicurarsi di garantire al programmatore tutto e solo lo spazio necessario per poter utilizzare la union. Nel nostro caso, infatti, il peso della union è dato da

max { sizeof(iip); sizeof(sip) }

Con particolare riferimento al nostro caso, abbiamo che sizeof(iip) == sizeof(sip) == 32 bit == 4 byte. A parte il peso, ribadisco che, contrariamente a quanto succede nella struct, i campi della union, fanno riferimento alla stessa zona di memoria. Se provassimo a rappresentare graficamente la nostra union, avremmo un risultato del genere:

Union02.jpg

Non vedo altre differenze con le struct; ma comunque, alla domanda “che differenze ci sono tra una struct e una union”, io risponderei “lascia perdere, non fa per te”.

Utilizzo tipico di una union

Questo paragrafo è tipico in una qualsiasi lezione sulle union degna di esistere. L’esempio che vi ho fatto in precedenza, usa una union per vedere in due modi diversi uno stesso tipo di dato. In realtà nulla vieta che in una union si inseriscano campi che non hanno nulla a che vedere l’uno con l’altro. Ad esempio potrei usare una union per metterci dentro un indirizzo ip oppure un appuntamento personale, in questo modo: struct ipv4 /* rappresentazione intel */ { BYTE v4; BYTE v3; BYTE v2; BYTE v1; };

struct appuntamento { char luogo[MAXLOAD]; char oggetto[MAXLOAD]; char data_e_ora[MAXLOAD]; };

union stranezza { struct ipv4 s_ipv4; struct appuntamento s_appuntamento; }; Chiaramente, un appuntamento e un indirizzo ip non c’azzeccano nulla l’uno con l’altro ma il codice è lecito e largamente utilizzato (ovviamente non con appuntamenti e indirizzi ip -.-). Usando cose di questo tipo, il programmatore, prima di utilizzare una union, deve sapere, a tempo di esecuzione, se questa contiene un appuntamento o un indirizzo IP. Per fare questo distinguo, spesso e volentieri, si incapsula la union in una struct che contiene, oltre ad essa, una variabile (intera o booleana, o un char.. o quel che vi pare) in grado di fornire il tipo di dato che viene inserito nella union. Ad esempio, potremmo scegliere di incapsulare la union stranezza in una struct di questo tipo: struct stranezza_wrapper { char type; union stranezza sw_stranezza; }; e decidere (ma è del tutto convenzionale e di libero arbitrio) che, ogni qualvolta t sia uguale a ‘i’, la union stranezza contenga un indirizzo ip, mentre quando t sia uguale a ‘a’, la union contenga un appuntamento. Un codice esemplare ha questa forma: int main(int argc, char **argv) { struct stranezza_wrapper sw; ... ...

if(sw.type == 'i') { printf("La union contiene il seguente indirizzo ip: %d.%d.%d.%d\n", sw.sw_stranezza.s_ipv4.v1, sw.sw_stranezza.s_ipv4.v2, sw.sw_stranezza.s_ipv4.v3, sw.sw_stranezza.s_ipv4.v4); } else if(sw.type == 'a') { printf("La union contiene il seguente appuntamento:\n"); printf("Luogo: %s\n", sw.sw_stranezza.s_appuntamento.luogo); printf("Oggetto: %s\n", sw.sw_stranezza.s_appuntamento.oggetto); printf("Data: %s\n", sw.sw_stranezza.s_appuntamento.data_e_ora); } else { printf("Dato della union non identificabile."); }

return 0; } Chiaramente, questa è la parte in cui il programma “legge” da una union il tipo di dato fornito da type. Ovviamente, per una questione di correttezza e consistenza, quando il programma compila (dunque le parti di “scrittura” della union) la union (e dunque la struct che la incapsula), deve occuparsi di settare correttamente anche il campo type, in modo da permettere al programma un’evoluzione che non dia origine a malfunzionamenti.

Un ultimo esempio

Riprendendo il discorso dell’indirizzo IP, voglio concludere mostrandovi come ottenere il numero di prima (tre miliardi e mezzo) senza usare la calcolatrice ma usando le union in senso inverso: compilo i vari campi e poi stampo la memoria col suo campo da intero senza segno: int main(int argc, char **argv) { union uipv4 myip;

/* setto i vari campi in modo human-like */ myip.sip.v1 = 212; myip.sip.v2 = 56; myip.sip.v3 = 3; myip.sip.v4 = 23;

/* stampo */ printf("L'ip specificato e' rappresentato dall'intero %u\n", myip.iip); system("pause"); return 0; } Quello che volevamo:

Union03.jpg

Proprio così, concludo con questa paginetta. Sinceramente, non me la sento di dedicare più di sei pagine ad una lezione di questo tipo. Se avete capito i puntatori e le struct, dovreste aver acquisito quell’elasticità mentale per apprendere più rapidamente anche il resto delle nozioni di C che sono via via spiegate. Come al solito, ho fatto di tutto per scrivere e mostrare i codici e le reazioni più significative o più tipiche dell’argomento.


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.